Extend legality testing to drop moves
[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, and (for the time being) no piece drops
6670         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6671             if( appData.testLegality )
6672             {   /* [HGM] Some more adjudications for obstinate engines */
6673                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6674                 static int moveCount = 6;
6675                 ChessMove result;
6676                 char *reason = NULL;
6677
6678                 /* Count what is on board. */
6679                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6680
6681                 /* Some material-based adjudications that have to be made before stalemate test */
6682                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6683                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6684                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6685                      if(canAdjudicate && appData.checkMates) {
6686                          if(engineOpponent)
6687                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6688                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6689                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6690                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6691                          return 1;
6692                      }
6693                 }
6694
6695                 /* Bare King in Shatranj (loses) or Losers (wins) */
6696                 if( nrW == 1 || nrB == 1) {
6697                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6698                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6699                      if(canAdjudicate && appData.checkMates) {
6700                          if(engineOpponent)
6701                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6702                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6703                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6704                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6705                          return 1;
6706                      }
6707                   } else
6708                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6709                   {    /* bare King */
6710                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6711                         if(canAdjudicate && appData.checkMates) {
6712                             /* but only adjudicate if adjudication enabled */
6713                             if(engineOpponent)
6714                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6715                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6716                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn, 
6717                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6718                             return 1;
6719                         }
6720                   }
6721                 } else bare = 1;
6722
6723
6724             // don't wait for engine to announce game end if we can judge ourselves
6725             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6726               case MT_CHECK:
6727                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6728                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6729                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6730                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6731                             checkCnt++;
6732                         if(checkCnt >= 2) {
6733                             reason = "Xboard adjudication: 3rd check";
6734                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6735                             break;
6736                         }
6737                     }
6738                 }
6739               case MT_NONE:
6740               default:
6741                 break;
6742               case MT_STALEMATE:
6743               case MT_STAINMATE:
6744                 reason = "Xboard adjudication: Stalemate";
6745                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6746                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6747                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6748                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6749                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6750                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6751                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6752                                                                         EP_CHECKMATE : EP_WINS);
6753                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6754                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6755                 }
6756                 break;
6757               case MT_CHECKMATE:
6758                 reason = "Xboard adjudication: Checkmate";
6759                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6760                 break;
6761             }
6762
6763                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6764                     case EP_STALEMATE:
6765                         result = GameIsDrawn; break;
6766                     case EP_CHECKMATE:
6767                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6768                     case EP_WINS:
6769                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6770                     default:
6771                         result = (ChessMove) 0;
6772                 }
6773                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6774                     if(engineOpponent)
6775                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6776                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6777                     GameEnds( result, reason, GE_XBOARD );
6778                     return 1;
6779                 }
6780
6781                 /* Next absolutely insufficient mating material. */
6782                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6783                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6784                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
6785
6786                      /* always flag draws, for judging claims */
6787                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6788
6789                      if(canAdjudicate && appData.materialDraws) {
6790                          /* but only adjudicate them if adjudication enabled */
6791                          if(engineOpponent) {
6792                            SendToProgram("force\n", engineOpponent); // suppress reply
6793                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6794                          }
6795                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6796                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6797                          return 1;
6798                      }
6799                 }
6800
6801                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6802                 if(gameInfo.variant == VariantXiangqi ?
6803                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
6804                  : nrW + nrB == 4 && 
6805                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6806                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
6807                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
6808                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6809                    ) ) {
6810                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6811                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6812                           if(engineOpponent) {
6813                             SendToProgram("force\n", engineOpponent); // suppress reply
6814                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6815                           }
6816                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6817                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6818                           return 1;
6819                      }
6820                 } else moveCount = 6;
6821             }
6822         }
6823           
6824         if (appData.debugMode) { int i;
6825             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6826                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6827                     appData.drawRepeats);
6828             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6829               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6830             
6831         }
6832
6833         // Repetition draws and 50-move rule can be applied independently of legality testing
6834
6835                 /* Check for rep-draws */
6836                 count = 0;
6837                 for(k = forwardMostMove-2;
6838                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6839                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6840                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6841                     k-=2)
6842                 {   int rights=0;
6843                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6844                         /* compare castling rights */
6845                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6846                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6847                                 rights++; /* King lost rights, while rook still had them */
6848                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6849                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6850                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6851                                    rights++; /* but at least one rook lost them */
6852                         }
6853                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6854                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6855                                 rights++; 
6856                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6857                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6858                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6859                                    rights++;
6860                         }
6861                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6862                             && appData.drawRepeats > 1) {
6863                              /* adjudicate after user-specified nr of repeats */
6864                              int result = GameIsDrawn;
6865                              char *details = "XBoard adjudication: repetition draw";
6866                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6867                                 // [HGM] xiangqi: check for forbidden perpetuals
6868                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6869                                 for(m=forwardMostMove; m>k; m-=2) {
6870                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6871                                         ourPerpetual = 0; // the current mover did not always check
6872                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6873                                         hisPerpetual = 0; // the opponent did not always check
6874                                 }
6875                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6876                                                                         ourPerpetual, hisPerpetual);
6877                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6878                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6879                                     details = "Xboard adjudication: perpetual checking";
6880                                 } else
6881                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
6882                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6883                                 } else
6884                                 // Now check for perpetual chases
6885                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6886                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6887                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6888                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6889                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6890                                         details = "Xboard adjudication: perpetual chasing";
6891                                     } else
6892                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6893                                         break; // Abort repetition-checking loop.
6894                                 }
6895                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6896                              }
6897                              if(engineOpponent) {
6898                                SendToProgram("force\n", engineOpponent); // suppress reply
6899                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6900                              }
6901                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6902                              GameEnds( result, details, GE_XBOARD );
6903                              return 1;
6904                         }
6905                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6906                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6907                     }
6908                 }
6909
6910                 /* Now we test for 50-move draws. Determine ply count */
6911                 count = forwardMostMove;
6912                 /* look for last irreversble move */
6913                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6914                     count--;
6915                 /* if we hit starting position, add initial plies */
6916                 if( count == backwardMostMove )
6917                     count -= initialRulePlies;
6918                 count = forwardMostMove - count; 
6919                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
6920                         // adjust reversible move counter for checks in Xiangqi
6921                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
6922                         if(i < backwardMostMove) i = backwardMostMove;
6923                         while(i <= forwardMostMove) {
6924                                 lastCheck = inCheck; // check evasion does not count
6925                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
6926                                 if(inCheck || lastCheck) count--; // check does not count
6927                                 i++;
6928                         }
6929                 }
6930                 if( count >= 100)
6931                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6932                          /* this is used to judge if draw claims are legal */
6933                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6934                          if(engineOpponent) {
6935                            SendToProgram("force\n", engineOpponent); // suppress reply
6936                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6937                          }
6938                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6939                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6940                          return 1;
6941                 }
6942
6943                 /* if draw offer is pending, treat it as a draw claim
6944                  * when draw condition present, to allow engines a way to
6945                  * claim draws before making their move to avoid a race
6946                  * condition occurring after their move
6947                  */
6948                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6949                          char *p = NULL;
6950                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6951                              p = "Draw claim: 50-move rule";
6952                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6953                              p = "Draw claim: 3-fold repetition";
6954                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6955                              p = "Draw claim: insufficient mating material";
6956                          if( p != NULL && canAdjudicate) {
6957                              if(engineOpponent) {
6958                                SendToProgram("force\n", engineOpponent); // suppress reply
6959                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6960                              }
6961                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6962                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6963                              return 1;
6964                          }
6965                 }
6966
6967                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6968                     if(engineOpponent) {
6969                       SendToProgram("force\n", engineOpponent); // suppress reply
6970                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6971                     }
6972                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6973                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6974                     return 1;
6975                 }
6976         return 0;
6977 }
6978
6979 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6980 {   // [HGM] book: this routine intercepts moves to simulate book replies
6981     char *bookHit = NULL;
6982
6983     //first determine if the incoming move brings opponent into his book
6984     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6985         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6986     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6987     if(bookHit != NULL && !cps->bookSuspend) {
6988         // make sure opponent is not going to reply after receiving move to book position
6989         SendToProgram("force\n", cps);
6990         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6991     }
6992     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6993     // now arrange restart after book miss
6994     if(bookHit) {
6995         // after a book hit we never send 'go', and the code after the call to this routine
6996         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6997         char buf[MSG_SIZ];
6998         sprintf(buf, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
6999         SendToProgram(buf, cps);
7000         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7001     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7002         SendToProgram("go\n", cps);
7003         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7004     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7005         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7006             SendToProgram("go\n", cps); 
7007         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7008     }
7009     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7010 }
7011
7012 char *savedMessage;
7013 ChessProgramState *savedState;
7014 void DeferredBookMove(void)
7015 {
7016         if(savedState->lastPing != savedState->lastPong)
7017                     ScheduleDelayedEvent(DeferredBookMove, 10);
7018         else
7019         HandleMachineMove(savedMessage, savedState);
7020 }
7021
7022 void
7023 HandleMachineMove(message, cps)
7024      char *message;
7025      ChessProgramState *cps;
7026 {
7027     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7028     char realname[MSG_SIZ];
7029     int fromX, fromY, toX, toY;
7030     ChessMove moveType;
7031     char promoChar;
7032     char *p;
7033     int machineWhite;
7034     char *bookHit;
7035
7036     cps->userError = 0;
7037
7038 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7039     /*
7040      * Kludge to ignore BEL characters
7041      */
7042     while (*message == '\007') message++;
7043
7044     /*
7045      * [HGM] engine debug message: ignore lines starting with '#' character
7046      */
7047     if(cps->debug && *message == '#') return;
7048
7049     /*
7050      * Look for book output
7051      */
7052     if (cps == &first && bookRequested) {
7053         if (message[0] == '\t' || message[0] == ' ') {
7054             /* Part of the book output is here; append it */
7055             strcat(bookOutput, message);
7056             strcat(bookOutput, "  \n");
7057             return;
7058         } else if (bookOutput[0] != NULLCHAR) {
7059             /* All of book output has arrived; display it */
7060             char *p = bookOutput;
7061             while (*p != NULLCHAR) {
7062                 if (*p == '\t') *p = ' ';
7063                 p++;
7064             }
7065             DisplayInformation(bookOutput);
7066             bookRequested = FALSE;
7067             /* Fall through to parse the current output */
7068         }
7069     }
7070
7071     /*
7072      * Look for machine move.
7073      */
7074     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7075         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
7076     {
7077         /* This method is only useful on engines that support ping */
7078         if (cps->lastPing != cps->lastPong) {
7079           if (gameMode == BeginningOfGame) {
7080             /* Extra move from before last new; ignore */
7081             if (appData.debugMode) {
7082                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7083             }
7084           } else {
7085             if (appData.debugMode) {
7086                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7087                         cps->which, gameMode);
7088             }
7089
7090             SendToProgram("undo\n", cps);
7091           }
7092           return;
7093         }
7094
7095         switch (gameMode) {
7096           case BeginningOfGame:
7097             /* Extra move from before last reset; ignore */
7098             if (appData.debugMode) {
7099                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7100             }
7101             return;
7102
7103           case EndOfGame:
7104           case IcsIdle:
7105           default:
7106             /* Extra move after we tried to stop.  The mode test is
7107                not a reliable way of detecting this problem, but it's
7108                the best we can do on engines that don't support ping.
7109             */
7110             if (appData.debugMode) {
7111                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7112                         cps->which, gameMode);
7113             }
7114             SendToProgram("undo\n", cps);
7115             return;
7116
7117           case MachinePlaysWhite:
7118           case IcsPlayingWhite:
7119             machineWhite = TRUE;
7120             break;
7121
7122           case MachinePlaysBlack:
7123           case IcsPlayingBlack:
7124             machineWhite = FALSE;
7125             break;
7126
7127           case TwoMachinesPlay:
7128             machineWhite = (cps->twoMachinesColor[0] == 'w');
7129             break;
7130         }
7131         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7132             if (appData.debugMode) {
7133                 fprintf(debugFP,
7134                         "Ignoring move out of turn by %s, gameMode %d"
7135                         ", forwardMost %d\n",
7136                         cps->which, gameMode, forwardMostMove);
7137             }
7138             return;
7139         }
7140
7141     if (appData.debugMode) { int f = forwardMostMove;
7142         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7143                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7144                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7145     }
7146         if(cps->alphaRank) AlphaRank(machineMove, 4);
7147         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7148                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7149             /* Machine move could not be parsed; ignore it. */
7150             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7151                     machineMove, cps->which);
7152             DisplayError(buf1, 0);
7153             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7154                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7155             if (gameMode == TwoMachinesPlay) {
7156               GameEnds(machineWhite ? BlackWins : WhiteWins,
7157                        buf1, GE_XBOARD);
7158             }
7159             return;
7160         }
7161
7162         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7163         /* So we have to redo legality test with true e.p. status here,  */
7164         /* to make sure an illegal e.p. capture does not slip through,   */
7165         /* to cause a forfeit on a justified illegal-move complaint      */
7166         /* of the opponent.                                              */
7167         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7168            ChessMove moveType;
7169            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7170                              fromY, fromX, toY, toX, promoChar);
7171             if (appData.debugMode) {
7172                 int i;
7173                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7174                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7175                 fprintf(debugFP, "castling rights\n");
7176             }
7177             if(moveType == IllegalMove) {
7178                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7179                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7180                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7181                            buf1, GE_XBOARD);
7182                 return;
7183            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7184            /* [HGM] Kludge to handle engines that send FRC-style castling
7185               when they shouldn't (like TSCP-Gothic) */
7186            switch(moveType) {
7187              case WhiteASideCastleFR:
7188              case BlackASideCastleFR:
7189                toX+=2;
7190                currentMoveString[2]++;
7191                break;
7192              case WhiteHSideCastleFR:
7193              case BlackHSideCastleFR:
7194                toX--;
7195                currentMoveString[2]--;
7196                break;
7197              default: ; // nothing to do, but suppresses warning of pedantic compilers
7198            }
7199         }
7200         hintRequested = FALSE;
7201         lastHint[0] = NULLCHAR;
7202         bookRequested = FALSE;
7203         /* Program may be pondering now */
7204         cps->maybeThinking = TRUE;
7205         if (cps->sendTime == 2) cps->sendTime = 1;
7206         if (cps->offeredDraw) cps->offeredDraw--;
7207
7208         /* currentMoveString is set as a side-effect of ParseOneMove */
7209         strcpy(machineMove, currentMoveString);
7210         strcat(machineMove, "\n");
7211         strcpy(moveList[forwardMostMove], machineMove);
7212
7213         /* [AS] Save move info*/
7214         pvInfoList[ forwardMostMove ].score = programStats.score;
7215         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7216         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7217
7218         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7219
7220         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7221         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7222             int count = 0;
7223
7224             while( count < adjudicateLossPlies ) {
7225                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7226
7227                 if( count & 1 ) {
7228                     score = -score; /* Flip score for winning side */
7229                 }
7230
7231                 if( score > adjudicateLossThreshold ) {
7232                     break;
7233                 }
7234
7235                 count++;
7236             }
7237
7238             if( count >= adjudicateLossPlies ) {
7239                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7240
7241                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7242                     "Xboard adjudication", 
7243                     GE_XBOARD );
7244
7245                 return;
7246             }
7247         }
7248
7249         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7250
7251 #if ZIPPY
7252         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7253             first.initDone) {
7254           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7255                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7256                 SendToICS("draw ");
7257                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7258           }
7259           SendMoveToICS(moveType, fromX, fromY, toX, toY);
7260           ics_user_moved = 1;
7261           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7262                 char buf[3*MSG_SIZ];
7263
7264                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7265                         programStats.score / 100.,
7266                         programStats.depth,
7267                         programStats.time / 100.,
7268                         (unsigned int)programStats.nodes,
7269                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7270                         programStats.movelist);
7271                 SendToICS(buf);
7272 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7273           }
7274         }
7275 #endif
7276
7277         /* [AS] Clear stats for next move */
7278         ClearProgramStats();
7279         thinkOutput[0] = NULLCHAR;
7280         hiddenThinkOutputState = 0;
7281
7282         bookHit = NULL;
7283         if (gameMode == TwoMachinesPlay) {
7284             /* [HGM] relaying draw offers moved to after reception of move */
7285             /* and interpreting offer as claim if it brings draw condition */
7286             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7287                 SendToProgram("draw\n", cps->other);
7288             }
7289             if (cps->other->sendTime) {
7290                 SendTimeRemaining(cps->other,
7291                                   cps->other->twoMachinesColor[0] == 'w');
7292             }
7293             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7294             if (firstMove && !bookHit) {
7295                 firstMove = FALSE;
7296                 if (cps->other->useColors) {
7297                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7298                 }
7299                 SendToProgram("go\n", cps->other);
7300             }
7301             cps->other->maybeThinking = TRUE;
7302         }
7303
7304         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7305         
7306         if (!pausing && appData.ringBellAfterMoves) {
7307             RingBell();
7308         }
7309
7310         /* 
7311          * Reenable menu items that were disabled while
7312          * machine was thinking
7313          */
7314         if (gameMode != TwoMachinesPlay)
7315             SetUserThinkingEnables();
7316
7317         // [HGM] book: after book hit opponent has received move and is now in force mode
7318         // force the book reply into it, and then fake that it outputted this move by jumping
7319         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7320         if(bookHit) {
7321                 static char bookMove[MSG_SIZ]; // a bit generous?
7322
7323                 strcpy(bookMove, "move ");
7324                 strcat(bookMove, bookHit);
7325                 message = bookMove;
7326                 cps = cps->other;
7327                 programStats.nodes = programStats.depth = programStats.time = 
7328                 programStats.score = programStats.got_only_move = 0;
7329                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7330
7331                 if(cps->lastPing != cps->lastPong) {
7332                     savedMessage = message; // args for deferred call
7333                     savedState = cps;
7334                     ScheduleDelayedEvent(DeferredBookMove, 10);
7335                     return;
7336                 }
7337                 goto FakeBookMove;
7338         }
7339
7340         return;
7341     }
7342
7343     /* Set special modes for chess engines.  Later something general
7344      *  could be added here; for now there is just one kludge feature,
7345      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7346      *  when "xboard" is given as an interactive command.
7347      */
7348     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7349         cps->useSigint = FALSE;
7350         cps->useSigterm = FALSE;
7351     }
7352     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7353       ParseFeatures(message+8, cps);
7354       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7355     }
7356
7357     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7358      * want this, I was asked to put it in, and obliged.
7359      */
7360     if (!strncmp(message, "setboard ", 9)) {
7361         Board initial_position;
7362
7363         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7364
7365         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7366             DisplayError(_("Bad FEN received from engine"), 0);
7367             return ;
7368         } else {
7369            Reset(TRUE, FALSE);
7370            CopyBoard(boards[0], initial_position);
7371            initialRulePlies = FENrulePlies;
7372            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7373            else gameMode = MachinePlaysBlack;                 
7374            DrawPosition(FALSE, boards[currentMove]);
7375         }
7376         return;
7377     }
7378
7379     /*
7380      * Look for communication commands
7381      */
7382     if (!strncmp(message, "telluser ", 9)) {
7383         EscapeExpand(message+9, message+9); // [HGM] esc: allow escape sequences in popup box
7384         DisplayNote(message + 9);
7385         return;
7386     }
7387     if (!strncmp(message, "tellusererror ", 14)) {
7388         cps->userError = 1;
7389         EscapeExpand(message+14, message+14); // [HGM] esc: allow escape sequences in popup box
7390         DisplayError(message + 14, 0);
7391         return;
7392     }
7393     if (!strncmp(message, "tellopponent ", 13)) {
7394       if (appData.icsActive) {
7395         if (loggedOn) {
7396           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7397           SendToICS(buf1);
7398         }
7399       } else {
7400         DisplayNote(message + 13);
7401       }
7402       return;
7403     }
7404     if (!strncmp(message, "tellothers ", 11)) {
7405       if (appData.icsActive) {
7406         if (loggedOn) {
7407           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7408           SendToICS(buf1);
7409         }
7410       }
7411       return;
7412     }
7413     if (!strncmp(message, "tellall ", 8)) {
7414       if (appData.icsActive) {
7415         if (loggedOn) {
7416           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7417           SendToICS(buf1);
7418         }
7419       } else {
7420         DisplayNote(message + 8);
7421       }
7422       return;
7423     }
7424     if (strncmp(message, "warning", 7) == 0) {
7425         /* Undocumented feature, use tellusererror in new code */
7426         DisplayError(message, 0);
7427         return;
7428     }
7429     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7430         strcpy(realname, cps->tidy);
7431         strcat(realname, " query");
7432         AskQuestion(realname, buf2, buf1, cps->pr);
7433         return;
7434     }
7435     /* Commands from the engine directly to ICS.  We don't allow these to be 
7436      *  sent until we are logged on. Crafty kibitzes have been known to 
7437      *  interfere with the login process.
7438      */
7439     if (loggedOn) {
7440         if (!strncmp(message, "tellics ", 8)) {
7441             SendToICS(message + 8);
7442             SendToICS("\n");
7443             return;
7444         }
7445         if (!strncmp(message, "tellicsnoalias ", 15)) {
7446             SendToICS(ics_prefix);
7447             SendToICS(message + 15);
7448             SendToICS("\n");
7449             return;
7450         }
7451         /* The following are for backward compatibility only */
7452         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7453             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7454             SendToICS(ics_prefix);
7455             SendToICS(message);
7456             SendToICS("\n");
7457             return;
7458         }
7459     }
7460     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7461         return;
7462     }
7463     /*
7464      * If the move is illegal, cancel it and redraw the board.
7465      * Also deal with other error cases.  Matching is rather loose
7466      * here to accommodate engines written before the spec.
7467      */
7468     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7469         strncmp(message, "Error", 5) == 0) {
7470         if (StrStr(message, "name") || 
7471             StrStr(message, "rating") || StrStr(message, "?") ||
7472             StrStr(message, "result") || StrStr(message, "board") ||
7473             StrStr(message, "bk") || StrStr(message, "computer") ||
7474             StrStr(message, "variant") || StrStr(message, "hint") ||
7475             StrStr(message, "random") || StrStr(message, "depth") ||
7476             StrStr(message, "accepted")) {
7477             return;
7478         }
7479         if (StrStr(message, "protover")) {
7480           /* Program is responding to input, so it's apparently done
7481              initializing, and this error message indicates it is
7482              protocol version 1.  So we don't need to wait any longer
7483              for it to initialize and send feature commands. */
7484           FeatureDone(cps, 1);
7485           cps->protocolVersion = 1;
7486           return;
7487         }
7488         cps->maybeThinking = FALSE;
7489
7490         if (StrStr(message, "draw")) {
7491             /* Program doesn't have "draw" command */
7492             cps->sendDrawOffers = 0;
7493             return;
7494         }
7495         if (cps->sendTime != 1 &&
7496             (StrStr(message, "time") || StrStr(message, "otim"))) {
7497           /* Program apparently doesn't have "time" or "otim" command */
7498           cps->sendTime = 0;
7499           return;
7500         }
7501         if (StrStr(message, "analyze")) {
7502             cps->analysisSupport = FALSE;
7503             cps->analyzing = FALSE;
7504             Reset(FALSE, TRUE);
7505             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7506             DisplayError(buf2, 0);
7507             return;
7508         }
7509         if (StrStr(message, "(no matching move)st")) {
7510           /* Special kludge for GNU Chess 4 only */
7511           cps->stKludge = TRUE;
7512           SendTimeControl(cps, movesPerSession, timeControl,
7513                           timeIncrement, appData.searchDepth,
7514                           searchTime);
7515           return;
7516         }
7517         if (StrStr(message, "(no matching move)sd")) {
7518           /* Special kludge for GNU Chess 4 only */
7519           cps->sdKludge = TRUE;
7520           SendTimeControl(cps, movesPerSession, timeControl,
7521                           timeIncrement, appData.searchDepth,
7522                           searchTime);
7523           return;
7524         }
7525         if (!StrStr(message, "llegal")) {
7526             return;
7527         }
7528         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7529             gameMode == IcsIdle) return;
7530         if (forwardMostMove <= backwardMostMove) return;
7531         if (pausing) PauseEvent();
7532       if(appData.forceIllegal) {
7533             // [HGM] illegal: machine refused move; force position after move into it
7534           SendToProgram("force\n", cps);
7535           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7536                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7537                 // when black is to move, while there might be nothing on a2 or black
7538                 // might already have the move. So send the board as if white has the move.
7539                 // But first we must change the stm of the engine, as it refused the last move
7540                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7541                 if(WhiteOnMove(forwardMostMove)) {
7542                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7543                     SendBoard(cps, forwardMostMove); // kludgeless board
7544                 } else {
7545                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7546                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7547                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7548                 }
7549           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7550             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7551                  gameMode == TwoMachinesPlay)
7552               SendToProgram("go\n", cps);
7553             return;
7554       } else
7555         if (gameMode == PlayFromGameFile) {
7556             /* Stop reading this game file */
7557             gameMode = EditGame;
7558             ModeHighlight();
7559         }
7560         currentMove = forwardMostMove-1;
7561         DisplayMove(currentMove-1); /* before DisplayMoveError */
7562         SwitchClocks(forwardMostMove-1); // [HGM] race
7563         DisplayBothClocks();
7564         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7565                 parseList[currentMove], cps->which);
7566         DisplayMoveError(buf1);
7567         DrawPosition(FALSE, boards[currentMove]);
7568
7569         /* [HGM] illegal-move claim should forfeit game when Xboard */
7570         /* only passes fully legal moves                            */
7571         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7572             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7573                                 "False illegal-move claim", GE_XBOARD );
7574         }
7575         return;
7576     }
7577     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7578         /* Program has a broken "time" command that
7579            outputs a string not ending in newline.
7580            Don't use it. */
7581         cps->sendTime = 0;
7582     }
7583     
7584     /*
7585      * If chess program startup fails, exit with an error message.
7586      * Attempts to recover here are futile.
7587      */
7588     if ((StrStr(message, "unknown host") != NULL)
7589         || (StrStr(message, "No remote directory") != NULL)
7590         || (StrStr(message, "not found") != NULL)
7591         || (StrStr(message, "No such file") != NULL)
7592         || (StrStr(message, "can't alloc") != NULL)
7593         || (StrStr(message, "Permission denied") != NULL)) {
7594
7595         cps->maybeThinking = FALSE;
7596         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7597                 cps->which, cps->program, cps->host, message);
7598         RemoveInputSource(cps->isr);
7599         DisplayFatalError(buf1, 0, 1);
7600         return;
7601     }
7602     
7603     /* 
7604      * Look for hint output
7605      */
7606     if (sscanf(message, "Hint: %s", buf1) == 1) {
7607         if (cps == &first && hintRequested) {
7608             hintRequested = FALSE;
7609             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7610                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7611                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7612                                     PosFlags(forwardMostMove),
7613                                     fromY, fromX, toY, toX, promoChar, buf1);
7614                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7615                 DisplayInformation(buf2);
7616             } else {
7617                 /* Hint move could not be parsed!? */
7618               snprintf(buf2, sizeof(buf2),
7619                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7620                         buf1, cps->which);
7621                 DisplayError(buf2, 0);
7622             }
7623         } else {
7624             strcpy(lastHint, buf1);
7625         }
7626         return;
7627     }
7628
7629     /*
7630      * Ignore other messages if game is not in progress
7631      */
7632     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7633         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7634
7635     /*
7636      * look for win, lose, draw, or draw offer
7637      */
7638     if (strncmp(message, "1-0", 3) == 0) {
7639         char *p, *q, *r = "";
7640         p = strchr(message, '{');
7641         if (p) {
7642             q = strchr(p, '}');
7643             if (q) {
7644                 *q = NULLCHAR;
7645                 r = p + 1;
7646             }
7647         }
7648         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7649         return;
7650     } else if (strncmp(message, "0-1", 3) == 0) {
7651         char *p, *q, *r = "";
7652         p = strchr(message, '{');
7653         if (p) {
7654             q = strchr(p, '}');
7655             if (q) {
7656                 *q = NULLCHAR;
7657                 r = p + 1;
7658             }
7659         }
7660         /* Kludge for Arasan 4.1 bug */
7661         if (strcmp(r, "Black resigns") == 0) {
7662             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7663             return;
7664         }
7665         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7666         return;
7667     } else if (strncmp(message, "1/2", 3) == 0) {
7668         char *p, *q, *r = "";
7669         p = strchr(message, '{');
7670         if (p) {
7671             q = strchr(p, '}');
7672             if (q) {
7673                 *q = NULLCHAR;
7674                 r = p + 1;
7675             }
7676         }
7677             
7678         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7679         return;
7680
7681     } else if (strncmp(message, "White resign", 12) == 0) {
7682         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7683         return;
7684     } else if (strncmp(message, "Black resign", 12) == 0) {
7685         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7686         return;
7687     } else if (strncmp(message, "White matches", 13) == 0 ||
7688                strncmp(message, "Black matches", 13) == 0   ) {
7689         /* [HGM] ignore GNUShogi noises */
7690         return;
7691     } else if (strncmp(message, "White", 5) == 0 &&
7692                message[5] != '(' &&
7693                StrStr(message, "Black") == NULL) {
7694         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7695         return;
7696     } else if (strncmp(message, "Black", 5) == 0 &&
7697                message[5] != '(') {
7698         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7699         return;
7700     } else if (strcmp(message, "resign") == 0 ||
7701                strcmp(message, "computer resigns") == 0) {
7702         switch (gameMode) {
7703           case MachinePlaysBlack:
7704           case IcsPlayingBlack:
7705             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7706             break;
7707           case MachinePlaysWhite:
7708           case IcsPlayingWhite:
7709             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7710             break;
7711           case TwoMachinesPlay:
7712             if (cps->twoMachinesColor[0] == 'w')
7713               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7714             else
7715               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7716             break;
7717           default:
7718             /* can't happen */
7719             break;
7720         }
7721         return;
7722     } else if (strncmp(message, "opponent mates", 14) == 0) {
7723         switch (gameMode) {
7724           case MachinePlaysBlack:
7725           case IcsPlayingBlack:
7726             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7727             break;
7728           case MachinePlaysWhite:
7729           case IcsPlayingWhite:
7730             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7731             break;
7732           case TwoMachinesPlay:
7733             if (cps->twoMachinesColor[0] == 'w')
7734               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7735             else
7736               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7737             break;
7738           default:
7739             /* can't happen */
7740             break;
7741         }
7742         return;
7743     } else if (strncmp(message, "computer mates", 14) == 0) {
7744         switch (gameMode) {
7745           case MachinePlaysBlack:
7746           case IcsPlayingBlack:
7747             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7748             break;
7749           case MachinePlaysWhite:
7750           case IcsPlayingWhite:
7751             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7752             break;
7753           case TwoMachinesPlay:
7754             if (cps->twoMachinesColor[0] == 'w')
7755               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7756             else
7757               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7758             break;
7759           default:
7760             /* can't happen */
7761             break;
7762         }
7763         return;
7764     } else if (strncmp(message, "checkmate", 9) == 0) {
7765         if (WhiteOnMove(forwardMostMove)) {
7766             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7767         } else {
7768             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7769         }
7770         return;
7771     } else if (strstr(message, "Draw") != NULL ||
7772                strstr(message, "game is a draw") != NULL) {
7773         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7774         return;
7775     } else if (strstr(message, "offer") != NULL &&
7776                strstr(message, "draw") != NULL) {
7777 #if ZIPPY
7778         if (appData.zippyPlay && first.initDone) {
7779             /* Relay offer to ICS */
7780             SendToICS(ics_prefix);
7781             SendToICS("draw\n");
7782         }
7783 #endif
7784         cps->offeredDraw = 2; /* valid until this engine moves twice */
7785         if (gameMode == TwoMachinesPlay) {
7786             if (cps->other->offeredDraw) {
7787                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7788             /* [HGM] in two-machine mode we delay relaying draw offer      */
7789             /* until after we also have move, to see if it is really claim */
7790             }
7791         } else if (gameMode == MachinePlaysWhite ||
7792                    gameMode == MachinePlaysBlack) {
7793           if (userOfferedDraw) {
7794             DisplayInformation(_("Machine accepts your draw offer"));
7795             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7796           } else {
7797             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7798           }
7799         }
7800     }
7801
7802     
7803     /*
7804      * Look for thinking output
7805      */
7806     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7807           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7808                                 ) {
7809         int plylev, mvleft, mvtot, curscore, time;
7810         char mvname[MOVE_LEN];
7811         u64 nodes; // [DM]
7812         char plyext;
7813         int ignore = FALSE;
7814         int prefixHint = FALSE;
7815         mvname[0] = NULLCHAR;
7816
7817         switch (gameMode) {
7818           case MachinePlaysBlack:
7819           case IcsPlayingBlack:
7820             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7821             break;
7822           case MachinePlaysWhite:
7823           case IcsPlayingWhite:
7824             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7825             break;
7826           case AnalyzeMode:
7827           case AnalyzeFile:
7828             break;
7829           case IcsObserving: /* [DM] icsEngineAnalyze */
7830             if (!appData.icsEngineAnalyze) ignore = TRUE;
7831             break;
7832           case TwoMachinesPlay:
7833             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7834                 ignore = TRUE;
7835             }
7836             break;
7837           default:
7838             ignore = TRUE;
7839             break;
7840         }
7841
7842         if (!ignore) {
7843             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7844             buf1[0] = NULLCHAR;
7845             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7846                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7847
7848                 if (plyext != ' ' && plyext != '\t') {
7849                     time *= 100;
7850                 }
7851
7852                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7853                 if( cps->scoreIsAbsolute && 
7854                     ( gameMode == MachinePlaysBlack ||
7855                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7856                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7857                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7858                      !WhiteOnMove(currentMove)
7859                     ) )
7860                 {
7861                     curscore = -curscore;
7862                 }
7863
7864
7865                 tempStats.depth = plylev;
7866                 tempStats.nodes = nodes;
7867                 tempStats.time = time;
7868                 tempStats.score = curscore;
7869                 tempStats.got_only_move = 0;
7870
7871                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7872                         int ticklen;
7873
7874                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7875                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7876                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7877                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7878                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7879                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7880                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7881                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7882                 }
7883
7884                 /* Buffer overflow protection */
7885                 if (buf1[0] != NULLCHAR) {
7886                     if (strlen(buf1) >= sizeof(tempStats.movelist)
7887                         && appData.debugMode) {
7888                         fprintf(debugFP,
7889                                 "PV is too long; using the first %u bytes.\n",
7890                                 (unsigned) sizeof(tempStats.movelist) - 1);
7891                     }
7892
7893                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist) );
7894                 } else {
7895                     sprintf(tempStats.movelist, " no PV\n");
7896                 }
7897
7898                 if (tempStats.seen_stat) {
7899                     tempStats.ok_to_send = 1;
7900                 }
7901
7902                 if (strchr(tempStats.movelist, '(') != NULL) {
7903                     tempStats.line_is_book = 1;
7904                     tempStats.nr_moves = 0;
7905                     tempStats.moves_left = 0;
7906                 } else {
7907                     tempStats.line_is_book = 0;
7908                 }
7909
7910                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
7911                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
7912
7913                 SendProgramStatsToFrontend( cps, &tempStats );
7914
7915                 /* 
7916                     [AS] Protect the thinkOutput buffer from overflow... this
7917                     is only useful if buf1 hasn't overflowed first!
7918                 */
7919                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7920                         plylev, 
7921                         (gameMode == TwoMachinesPlay ?
7922                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7923                         ((double) curscore) / 100.0,
7924                         prefixHint ? lastHint : "",
7925                         prefixHint ? " " : "" );
7926
7927                 if( buf1[0] != NULLCHAR ) {
7928                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7929
7930                     if( strlen(buf1) > max_len ) {
7931                         if( appData.debugMode) {
7932                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7933                         }
7934                         buf1[max_len+1] = '\0';
7935                     }
7936
7937                     strcat( thinkOutput, buf1 );
7938                 }
7939
7940                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7941                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7942                     DisplayMove(currentMove - 1);
7943                 }
7944                 return;
7945
7946             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7947                 /* crafty (9.25+) says "(only move) <move>"
7948                  * if there is only 1 legal move
7949                  */
7950                 sscanf(p, "(only move) %s", buf1);
7951                 sprintf(thinkOutput, "%s (only move)", buf1);
7952                 sprintf(programStats.movelist, "%s (only move)", buf1);
7953                 programStats.depth = 1;
7954                 programStats.nr_moves = 1;
7955                 programStats.moves_left = 1;
7956                 programStats.nodes = 1;
7957                 programStats.time = 1;
7958                 programStats.got_only_move = 1;
7959
7960                 /* Not really, but we also use this member to
7961                    mean "line isn't going to change" (Crafty
7962                    isn't searching, so stats won't change) */
7963                 programStats.line_is_book = 1;
7964
7965                 SendProgramStatsToFrontend( cps, &programStats );
7966                 
7967                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7968                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7969                     DisplayMove(currentMove - 1);
7970                 }
7971                 return;
7972             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7973                               &time, &nodes, &plylev, &mvleft,
7974                               &mvtot, mvname) >= 5) {
7975                 /* The stat01: line is from Crafty (9.29+) in response
7976                    to the "." command */
7977                 programStats.seen_stat = 1;
7978                 cps->maybeThinking = TRUE;
7979
7980                 if (programStats.got_only_move || !appData.periodicUpdates)
7981                   return;
7982
7983                 programStats.depth = plylev;
7984                 programStats.time = time;
7985                 programStats.nodes = nodes;
7986                 programStats.moves_left = mvleft;
7987                 programStats.nr_moves = mvtot;
7988                 strcpy(programStats.move_name, mvname);
7989                 programStats.ok_to_send = 1;
7990                 programStats.movelist[0] = '\0';
7991
7992                 SendProgramStatsToFrontend( cps, &programStats );
7993
7994                 return;
7995
7996             } else if (strncmp(message,"++",2) == 0) {
7997                 /* Crafty 9.29+ outputs this */
7998                 programStats.got_fail = 2;
7999                 return;
8000
8001             } else if (strncmp(message,"--",2) == 0) {
8002                 /* Crafty 9.29+ outputs this */
8003                 programStats.got_fail = 1;
8004                 return;
8005
8006             } else if (thinkOutput[0] != NULLCHAR &&
8007                        strncmp(message, "    ", 4) == 0) {
8008                 unsigned message_len;
8009
8010                 p = message;
8011                 while (*p && *p == ' ') p++;
8012
8013                 message_len = strlen( p );
8014
8015                 /* [AS] Avoid buffer overflow */
8016                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8017                     strcat(thinkOutput, " ");
8018                     strcat(thinkOutput, p);
8019                 }
8020
8021                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8022                     strcat(programStats.movelist, " ");
8023                     strcat(programStats.movelist, p);
8024                 }
8025
8026                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8027                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8028                     DisplayMove(currentMove - 1);
8029                 }
8030                 return;
8031             }
8032         }
8033         else {
8034             buf1[0] = NULLCHAR;
8035
8036             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8037                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
8038             {
8039                 ChessProgramStats cpstats;
8040
8041                 if (plyext != ' ' && plyext != '\t') {
8042                     time *= 100;
8043                 }
8044
8045                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8046                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8047                     curscore = -curscore;
8048                 }
8049
8050                 cpstats.depth = plylev;
8051                 cpstats.nodes = nodes;
8052                 cpstats.time = time;
8053                 cpstats.score = curscore;
8054                 cpstats.got_only_move = 0;
8055                 cpstats.movelist[0] = '\0';
8056
8057                 if (buf1[0] != NULLCHAR) {
8058                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
8059                 }
8060
8061                 cpstats.ok_to_send = 0;
8062                 cpstats.line_is_book = 0;
8063                 cpstats.nr_moves = 0;
8064                 cpstats.moves_left = 0;
8065
8066                 SendProgramStatsToFrontend( cps, &cpstats );
8067             }
8068         }
8069     }
8070 }
8071
8072
8073 /* Parse a game score from the character string "game", and
8074    record it as the history of the current game.  The game
8075    score is NOT assumed to start from the standard position. 
8076    The display is not updated in any way.
8077    */
8078 void
8079 ParseGameHistory(game)
8080      char *game;
8081 {
8082     ChessMove moveType;
8083     int fromX, fromY, toX, toY, boardIndex;
8084     char promoChar;
8085     char *p, *q;
8086     char buf[MSG_SIZ];
8087
8088     if (appData.debugMode)
8089       fprintf(debugFP, "Parsing game history: %s\n", game);
8090
8091     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8092     gameInfo.site = StrSave(appData.icsHost);
8093     gameInfo.date = PGNDate();
8094     gameInfo.round = StrSave("-");
8095
8096     /* Parse out names of players */
8097     while (*game == ' ') game++;
8098     p = buf;
8099     while (*game != ' ') *p++ = *game++;
8100     *p = NULLCHAR;
8101     gameInfo.white = StrSave(buf);
8102     while (*game == ' ') game++;
8103     p = buf;
8104     while (*game != ' ' && *game != '\n') *p++ = *game++;
8105     *p = NULLCHAR;
8106     gameInfo.black = StrSave(buf);
8107
8108     /* Parse moves */
8109     boardIndex = blackPlaysFirst ? 1 : 0;
8110     yynewstr(game);
8111     for (;;) {
8112         yyboardindex = boardIndex;
8113         moveType = (ChessMove) yylex();
8114         switch (moveType) {
8115           case IllegalMove:             /* maybe suicide chess, etc. */
8116   if (appData.debugMode) {
8117     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8118     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8119     setbuf(debugFP, NULL);
8120   }
8121           case WhitePromotionChancellor:
8122           case BlackPromotionChancellor:
8123           case WhitePromotionArchbishop:
8124           case BlackPromotionArchbishop:
8125           case WhitePromotionQueen:
8126           case BlackPromotionQueen:
8127           case WhitePromotionRook:
8128           case BlackPromotionRook:
8129           case WhitePromotionBishop:
8130           case BlackPromotionBishop:
8131           case WhitePromotionKnight:
8132           case BlackPromotionKnight:
8133           case WhitePromotionKing:
8134           case BlackPromotionKing:
8135           case WhiteNonPromotion:
8136           case BlackNonPromotion:
8137           case NormalMove:
8138           case WhiteCapturesEnPassant:
8139           case BlackCapturesEnPassant:
8140           case WhiteKingSideCastle:
8141           case WhiteQueenSideCastle:
8142           case BlackKingSideCastle:
8143           case BlackQueenSideCastle:
8144           case WhiteKingSideCastleWild:
8145           case WhiteQueenSideCastleWild:
8146           case BlackKingSideCastleWild:
8147           case BlackQueenSideCastleWild:
8148           /* PUSH Fabien */
8149           case WhiteHSideCastleFR:
8150           case WhiteASideCastleFR:
8151           case BlackHSideCastleFR:
8152           case BlackASideCastleFR:
8153           /* POP Fabien */
8154             fromX = currentMoveString[0] - AAA;
8155             fromY = currentMoveString[1] - ONE;
8156             toX = currentMoveString[2] - AAA;
8157             toY = currentMoveString[3] - ONE;
8158             promoChar = currentMoveString[4];
8159             break;
8160           case WhiteDrop:
8161           case BlackDrop:
8162             fromX = moveType == WhiteDrop ?
8163               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8164             (int) CharToPiece(ToLower(currentMoveString[0]));
8165             fromY = DROP_RANK;
8166             toX = currentMoveString[2] - AAA;
8167             toY = currentMoveString[3] - ONE;
8168             promoChar = NULLCHAR;
8169             break;
8170           case AmbiguousMove:
8171             /* bug? */
8172             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8173   if (appData.debugMode) {
8174     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8175     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8176     setbuf(debugFP, NULL);
8177   }
8178             DisplayError(buf, 0);
8179             return;
8180           case ImpossibleMove:
8181             /* bug? */
8182             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8183   if (appData.debugMode) {
8184     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8185     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8186     setbuf(debugFP, NULL);
8187   }
8188             DisplayError(buf, 0);
8189             return;
8190           case (ChessMove) 0:   /* end of file */
8191             if (boardIndex < backwardMostMove) {
8192                 /* Oops, gap.  How did that happen? */
8193                 DisplayError(_("Gap in move list"), 0);
8194                 return;
8195             }
8196             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8197             if (boardIndex > forwardMostMove) {
8198                 forwardMostMove = boardIndex;
8199             }
8200             return;
8201           case ElapsedTime:
8202             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8203                 strcat(parseList[boardIndex-1], " ");
8204                 strcat(parseList[boardIndex-1], yy_text);
8205             }
8206             continue;
8207           case Comment:
8208           case PGNTag:
8209           case NAG:
8210           default:
8211             /* ignore */
8212             continue;
8213           case WhiteWins:
8214           case BlackWins:
8215           case GameIsDrawn:
8216           case GameUnfinished:
8217             if (gameMode == IcsExamining) {
8218                 if (boardIndex < backwardMostMove) {
8219                     /* Oops, gap.  How did that happen? */
8220                     return;
8221                 }
8222                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8223                 return;
8224             }
8225             gameInfo.result = moveType;
8226             p = strchr(yy_text, '{');
8227             if (p == NULL) p = strchr(yy_text, '(');
8228             if (p == NULL) {
8229                 p = yy_text;
8230                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8231             } else {
8232                 q = strchr(p, *p == '{' ? '}' : ')');
8233                 if (q != NULL) *q = NULLCHAR;
8234                 p++;
8235             }
8236             gameInfo.resultDetails = StrSave(p);
8237             continue;
8238         }
8239         if (boardIndex >= forwardMostMove &&
8240             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8241             backwardMostMove = blackPlaysFirst ? 1 : 0;
8242             return;
8243         }
8244         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8245                                  fromY, fromX, toY, toX, promoChar,
8246                                  parseList[boardIndex]);
8247         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8248         /* currentMoveString is set as a side-effect of yylex */
8249         strcpy(moveList[boardIndex], currentMoveString);
8250         strcat(moveList[boardIndex], "\n");
8251         boardIndex++;
8252         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8253         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8254           case MT_NONE:
8255           case MT_STALEMATE:
8256           default:
8257             break;
8258           case MT_CHECK:
8259             if(gameInfo.variant != VariantShogi)
8260                 strcat(parseList[boardIndex - 1], "+");
8261             break;
8262           case MT_CHECKMATE:
8263           case MT_STAINMATE:
8264             strcat(parseList[boardIndex - 1], "#");
8265             break;
8266         }
8267     }
8268 }
8269
8270
8271 /* Apply a move to the given board  */
8272 void
8273 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8274      int fromX, fromY, toX, toY;
8275      int promoChar;
8276      Board board;
8277 {
8278   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8279   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8280
8281     /* [HGM] compute & store e.p. status and castling rights for new position */
8282     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8283
8284       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8285       oldEP = (signed char)board[EP_STATUS];
8286       board[EP_STATUS] = EP_NONE;
8287
8288       if( board[toY][toX] != EmptySquare ) 
8289            board[EP_STATUS] = EP_CAPTURE;  
8290
8291   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8292   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8293        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8294          
8295   if (fromY == DROP_RANK) {
8296         /* must be first */
8297         piece = board[toY][toX] = (ChessSquare) fromX;
8298   } else {
8299       int i;
8300
8301       if( board[fromY][fromX] == WhitePawn ) {
8302            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8303                board[EP_STATUS] = EP_PAWN_MOVE;
8304            if( toY-fromY==2) {
8305                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8306                         gameInfo.variant != VariantBerolina || toX < fromX)
8307                       board[EP_STATUS] = toX | berolina;
8308                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8309                         gameInfo.variant != VariantBerolina || toX > fromX) 
8310                       board[EP_STATUS] = toX;
8311            }
8312       } else 
8313       if( board[fromY][fromX] == BlackPawn ) {
8314            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8315                board[EP_STATUS] = EP_PAWN_MOVE; 
8316            if( toY-fromY== -2) {
8317                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8318                         gameInfo.variant != VariantBerolina || toX < fromX)
8319                       board[EP_STATUS] = toX | berolina;
8320                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8321                         gameInfo.variant != VariantBerolina || toX > fromX) 
8322                       board[EP_STATUS] = toX;
8323            }
8324        }
8325
8326        for(i=0; i<nrCastlingRights; i++) {
8327            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8328               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8329              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8330        }
8331
8332      if (fromX == toX && fromY == toY) return;
8333
8334      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8335      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8336      if(gameInfo.variant == VariantKnightmate)
8337          king += (int) WhiteUnicorn - (int) WhiteKing;
8338
8339     /* Code added by Tord: */
8340     /* FRC castling assumed when king captures friendly rook. */
8341     if (board[fromY][fromX] == WhiteKing &&
8342              board[toY][toX] == WhiteRook) {
8343       board[fromY][fromX] = EmptySquare;
8344       board[toY][toX] = EmptySquare;
8345       if(toX > fromX) {
8346         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8347       } else {
8348         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8349       }
8350     } else if (board[fromY][fromX] == BlackKing &&
8351                board[toY][toX] == BlackRook) {
8352       board[fromY][fromX] = EmptySquare;
8353       board[toY][toX] = EmptySquare;
8354       if(toX > fromX) {
8355         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8356       } else {
8357         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8358       }
8359     /* End of code added by Tord */
8360
8361     } else if (board[fromY][fromX] == king
8362         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8363         && toY == fromY && toX > fromX+1) {
8364         board[fromY][fromX] = EmptySquare;
8365         board[toY][toX] = king;
8366         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8367         board[fromY][BOARD_RGHT-1] = EmptySquare;
8368     } else if (board[fromY][fromX] == king
8369         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8370                && toY == fromY && toX < fromX-1) {
8371         board[fromY][fromX] = EmptySquare;
8372         board[toY][toX] = king;
8373         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8374         board[fromY][BOARD_LEFT] = EmptySquare;
8375     } else if (board[fromY][fromX] == WhitePawn
8376                && toY >= BOARD_HEIGHT-promoRank
8377                && gameInfo.variant != VariantXiangqi
8378                ) {
8379         /* white pawn promotion */
8380         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8381         if (board[toY][toX] == EmptySquare) {
8382             board[toY][toX] = WhiteQueen;
8383         }
8384         if(gameInfo.variant==VariantBughouse ||
8385            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8386             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8387         board[fromY][fromX] = EmptySquare;
8388     } else if ((fromY == BOARD_HEIGHT-4)
8389                && (toX != fromX)
8390                && gameInfo.variant != VariantXiangqi
8391                && gameInfo.variant != VariantBerolina
8392                && (board[fromY][fromX] == WhitePawn)
8393                && (board[toY][toX] == EmptySquare)) {
8394         board[fromY][fromX] = EmptySquare;
8395         board[toY][toX] = WhitePawn;
8396         captured = board[toY - 1][toX];
8397         board[toY - 1][toX] = EmptySquare;
8398     } else if ((fromY == BOARD_HEIGHT-4)
8399                && (toX == fromX)
8400                && gameInfo.variant == VariantBerolina
8401                && (board[fromY][fromX] == WhitePawn)
8402                && (board[toY][toX] == EmptySquare)) {
8403         board[fromY][fromX] = EmptySquare;
8404         board[toY][toX] = WhitePawn;
8405         if(oldEP & EP_BEROLIN_A) {
8406                 captured = board[fromY][fromX-1];
8407                 board[fromY][fromX-1] = EmptySquare;
8408         }else{  captured = board[fromY][fromX+1];
8409                 board[fromY][fromX+1] = EmptySquare;
8410         }
8411     } else if (board[fromY][fromX] == king
8412         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8413                && toY == fromY && toX > fromX+1) {
8414         board[fromY][fromX] = EmptySquare;
8415         board[toY][toX] = king;
8416         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8417         board[fromY][BOARD_RGHT-1] = EmptySquare;
8418     } else if (board[fromY][fromX] == king
8419         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8420                && toY == fromY && toX < fromX-1) {
8421         board[fromY][fromX] = EmptySquare;
8422         board[toY][toX] = king;
8423         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8424         board[fromY][BOARD_LEFT] = EmptySquare;
8425     } else if (fromY == 7 && fromX == 3
8426                && board[fromY][fromX] == BlackKing
8427                && toY == 7 && toX == 5) {
8428         board[fromY][fromX] = EmptySquare;
8429         board[toY][toX] = BlackKing;
8430         board[fromY][7] = EmptySquare;
8431         board[toY][4] = BlackRook;
8432     } else if (fromY == 7 && fromX == 3
8433                && board[fromY][fromX] == BlackKing
8434                && toY == 7 && toX == 1) {
8435         board[fromY][fromX] = EmptySquare;
8436         board[toY][toX] = BlackKing;
8437         board[fromY][0] = EmptySquare;
8438         board[toY][2] = BlackRook;
8439     } else if (board[fromY][fromX] == BlackPawn
8440                && toY < promoRank
8441                && gameInfo.variant != VariantXiangqi
8442                ) {
8443         /* black pawn promotion */
8444         board[toY][toX] = CharToPiece(ToLower(promoChar));
8445         if (board[toY][toX] == EmptySquare) {
8446             board[toY][toX] = BlackQueen;
8447         }
8448         if(gameInfo.variant==VariantBughouse ||
8449            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8450             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8451         board[fromY][fromX] = EmptySquare;
8452     } else if ((fromY == 3)
8453                && (toX != fromX)
8454                && gameInfo.variant != VariantXiangqi
8455                && gameInfo.variant != VariantBerolina
8456                && (board[fromY][fromX] == BlackPawn)
8457                && (board[toY][toX] == EmptySquare)) {
8458         board[fromY][fromX] = EmptySquare;
8459         board[toY][toX] = BlackPawn;
8460         captured = board[toY + 1][toX];
8461         board[toY + 1][toX] = EmptySquare;
8462     } else if ((fromY == 3)
8463                && (toX == fromX)
8464                && gameInfo.variant == VariantBerolina
8465                && (board[fromY][fromX] == BlackPawn)
8466                && (board[toY][toX] == EmptySquare)) {
8467         board[fromY][fromX] = EmptySquare;
8468         board[toY][toX] = BlackPawn;
8469         if(oldEP & EP_BEROLIN_A) {
8470                 captured = board[fromY][fromX-1];
8471                 board[fromY][fromX-1] = EmptySquare;
8472         }else{  captured = board[fromY][fromX+1];
8473                 board[fromY][fromX+1] = EmptySquare;
8474         }
8475     } else {
8476         board[toY][toX] = board[fromY][fromX];
8477         board[fromY][fromX] = EmptySquare;
8478     }
8479
8480     /* [HGM] now we promote for Shogi, if needed */
8481     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8482         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8483   }
8484
8485     if (gameInfo.holdingsWidth != 0) {
8486
8487       /* !!A lot more code needs to be written to support holdings  */
8488       /* [HGM] OK, so I have written it. Holdings are stored in the */
8489       /* penultimate board files, so they are automaticlly stored   */
8490       /* in the game history.                                       */
8491       if (fromY == DROP_RANK) {
8492         /* Delete from holdings, by decreasing count */
8493         /* and erasing image if necessary            */
8494         p = (int) fromX;
8495         if(p < (int) BlackPawn) { /* white drop */
8496              p -= (int)WhitePawn;
8497                  p = PieceToNumber((ChessSquare)p);
8498              if(p >= gameInfo.holdingsSize) p = 0;
8499              if(--board[p][BOARD_WIDTH-2] <= 0)
8500                   board[p][BOARD_WIDTH-1] = EmptySquare;
8501              if((int)board[p][BOARD_WIDTH-2] < 0)
8502                         board[p][BOARD_WIDTH-2] = 0;
8503         } else {                  /* black drop */
8504              p -= (int)BlackPawn;
8505                  p = PieceToNumber((ChessSquare)p);
8506              if(p >= gameInfo.holdingsSize) p = 0;
8507              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8508                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8509              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8510                         board[BOARD_HEIGHT-1-p][1] = 0;
8511         }
8512       }
8513       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8514           && gameInfo.variant != VariantBughouse        ) {
8515         /* [HGM] holdings: Add to holdings, if holdings exist */
8516         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8517                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8518                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8519         }
8520         p = (int) captured;
8521         if (p >= (int) BlackPawn) {
8522           p -= (int)BlackPawn;
8523           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8524                   /* in Shogi restore piece to its original  first */
8525                   captured = (ChessSquare) (DEMOTED captured);
8526                   p = DEMOTED p;
8527           }
8528           p = PieceToNumber((ChessSquare)p);
8529           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8530           board[p][BOARD_WIDTH-2]++;
8531           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8532         } else {
8533           p -= (int)WhitePawn;
8534           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8535                   captured = (ChessSquare) (DEMOTED captured);
8536                   p = DEMOTED p;
8537           }
8538           p = PieceToNumber((ChessSquare)p);
8539           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8540           board[BOARD_HEIGHT-1-p][1]++;
8541           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8542         }
8543       }
8544     } else if (gameInfo.variant == VariantAtomic) {
8545       if (captured != EmptySquare) {
8546         int y, x;
8547         for (y = toY-1; y <= toY+1; y++) {
8548           for (x = toX-1; x <= toX+1; x++) {
8549             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8550                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8551               board[y][x] = EmptySquare;
8552             }
8553           }
8554         }
8555         board[toY][toX] = EmptySquare;
8556       }
8557     }
8558     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8559         /* [HGM] Shogi promotions */
8560         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8561     }
8562
8563     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8564                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8565         // [HGM] superchess: take promotion piece out of holdings
8566         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8567         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8568             if(!--board[k][BOARD_WIDTH-2])
8569                 board[k][BOARD_WIDTH-1] = EmptySquare;
8570         } else {
8571             if(!--board[BOARD_HEIGHT-1-k][1])
8572                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8573         }
8574     }
8575
8576 }
8577
8578 /* Updates forwardMostMove */
8579 void
8580 MakeMove(fromX, fromY, toX, toY, promoChar)
8581      int fromX, fromY, toX, toY;
8582      int promoChar;
8583 {
8584 //    forwardMostMove++; // [HGM] bare: moved downstream
8585
8586     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8587         int timeLeft; static int lastLoadFlag=0; int king, piece;
8588         piece = boards[forwardMostMove][fromY][fromX];
8589         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8590         if(gameInfo.variant == VariantKnightmate)
8591             king += (int) WhiteUnicorn - (int) WhiteKing;
8592         if(forwardMostMove == 0) {
8593             if(blackPlaysFirst) 
8594                 fprintf(serverMoves, "%s;", second.tidy);
8595             fprintf(serverMoves, "%s;", first.tidy);
8596             if(!blackPlaysFirst) 
8597                 fprintf(serverMoves, "%s;", second.tidy);
8598         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8599         lastLoadFlag = loadFlag;
8600         // print base move
8601         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8602         // print castling suffix
8603         if( toY == fromY && piece == king ) {
8604             if(toX-fromX > 1)
8605                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8606             if(fromX-toX >1)
8607                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8608         }
8609         // e.p. suffix
8610         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8611              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8612              boards[forwardMostMove][toY][toX] == EmptySquare
8613              && fromX != toX && fromY != toY)
8614                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8615         // promotion suffix
8616         if(promoChar != NULLCHAR)
8617                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8618         if(!loadFlag) {
8619             fprintf(serverMoves, "/%d/%d",
8620                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8621             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8622             else                      timeLeft = blackTimeRemaining/1000;
8623             fprintf(serverMoves, "/%d", timeLeft);
8624         }
8625         fflush(serverMoves);
8626     }
8627
8628     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8629       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8630                         0, 1);
8631       return;
8632     }
8633     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8634     if (commentList[forwardMostMove+1] != NULL) {
8635         free(commentList[forwardMostMove+1]);
8636         commentList[forwardMostMove+1] = NULL;
8637     }
8638     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8639     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8640     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8641     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8642     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8643     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8644     gameInfo.result = GameUnfinished;
8645     if (gameInfo.resultDetails != NULL) {
8646         free(gameInfo.resultDetails);
8647         gameInfo.resultDetails = NULL;
8648     }
8649     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8650                               moveList[forwardMostMove - 1]);
8651     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8652                              PosFlags(forwardMostMove - 1),
8653                              fromY, fromX, toY, toX, promoChar,
8654                              parseList[forwardMostMove - 1]);
8655     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8656       case MT_NONE:
8657       case MT_STALEMATE:
8658       default:
8659         break;
8660       case MT_CHECK:
8661         if(gameInfo.variant != VariantShogi)
8662             strcat(parseList[forwardMostMove - 1], "+");
8663         break;
8664       case MT_CHECKMATE:
8665       case MT_STAINMATE:
8666         strcat(parseList[forwardMostMove - 1], "#");
8667         break;
8668     }
8669     if (appData.debugMode) {
8670         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8671     }
8672
8673 }
8674
8675 /* Updates currentMove if not pausing */
8676 void
8677 ShowMove(fromX, fromY, toX, toY)
8678 {
8679     int instant = (gameMode == PlayFromGameFile) ?
8680         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8681     if(appData.noGUI) return;
8682     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8683         if (!instant) {
8684             if (forwardMostMove == currentMove + 1) {
8685                 AnimateMove(boards[forwardMostMove - 1],
8686                             fromX, fromY, toX, toY);
8687             }
8688             if (appData.highlightLastMove) {
8689                 SetHighlights(fromX, fromY, toX, toY);
8690             }
8691         }
8692         currentMove = forwardMostMove;
8693     }
8694
8695     if (instant) return;
8696
8697     DisplayMove(currentMove - 1);
8698     DrawPosition(FALSE, boards[currentMove]);
8699     DisplayBothClocks();
8700     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8701 }
8702
8703 void SendEgtPath(ChessProgramState *cps)
8704 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8705         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8706
8707         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8708
8709         while(*p) {
8710             char c, *q = name+1, *r, *s;
8711
8712             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8713             while(*p && *p != ',') *q++ = *p++;
8714             *q++ = ':'; *q = 0;
8715             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8716                 strcmp(name, ",nalimov:") == 0 ) {
8717                 // take nalimov path from the menu-changeable option first, if it is defined
8718                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8719                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8720             } else
8721             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8722                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8723                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8724                 s = r = StrStr(s, ":") + 1; // beginning of path info
8725                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8726                 c = *r; *r = 0;             // temporarily null-terminate path info
8727                     *--q = 0;               // strip of trailig ':' from name
8728                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8729                 *r = c;
8730                 SendToProgram(buf,cps);     // send egtbpath command for this format
8731             }
8732             if(*p == ',') p++; // read away comma to position for next format name
8733         }
8734 }
8735
8736 void
8737 InitChessProgram(cps, setup)
8738      ChessProgramState *cps;
8739      int setup; /* [HGM] needed to setup FRC opening position */
8740 {
8741     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8742     if (appData.noChessProgram) return;
8743     hintRequested = FALSE;
8744     bookRequested = FALSE;
8745
8746     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8747     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8748     if(cps->memSize) { /* [HGM] memory */
8749         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8750         SendToProgram(buf, cps);
8751     }
8752     SendEgtPath(cps); /* [HGM] EGT */
8753     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8754         sprintf(buf, "cores %d\n", appData.smpCores);
8755         SendToProgram(buf, cps);
8756     }
8757
8758     SendToProgram(cps->initString, cps);
8759     if (gameInfo.variant != VariantNormal &&
8760         gameInfo.variant != VariantLoadable
8761         /* [HGM] also send variant if board size non-standard */
8762         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8763                                             ) {
8764       char *v = VariantName(gameInfo.variant);
8765       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8766         /* [HGM] in protocol 1 we have to assume all variants valid */
8767         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8768         DisplayFatalError(buf, 0, 1);
8769         return;
8770       }
8771
8772       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8773       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8774       if( gameInfo.variant == VariantXiangqi )
8775            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8776       if( gameInfo.variant == VariantShogi )
8777            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8778       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8779            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8780       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8781                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8782            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8783       if( gameInfo.variant == VariantCourier )
8784            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8785       if( gameInfo.variant == VariantSuper )
8786            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8787       if( gameInfo.variant == VariantGreat )
8788            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8789
8790       if(overruled) {
8791            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8792                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8793            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8794            if(StrStr(cps->variants, b) == NULL) { 
8795                // specific sized variant not known, check if general sizing allowed
8796                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8797                    if(StrStr(cps->variants, "boardsize") == NULL) {
8798                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8799                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8800                        DisplayFatalError(buf, 0, 1);
8801                        return;
8802                    }
8803                    /* [HGM] here we really should compare with the maximum supported board size */
8804                }
8805            }
8806       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8807       sprintf(buf, "variant %s\n", b);
8808       SendToProgram(buf, cps);
8809     }
8810     currentlyInitializedVariant = gameInfo.variant;
8811
8812     /* [HGM] send opening position in FRC to first engine */
8813     if(setup) {
8814           SendToProgram("force\n", cps);
8815           SendBoard(cps, 0);
8816           /* engine is now in force mode! Set flag to wake it up after first move. */
8817           setboardSpoiledMachineBlack = 1;
8818     }
8819
8820     if (cps->sendICS) {
8821       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8822       SendToProgram(buf, cps);
8823     }
8824     cps->maybeThinking = FALSE;
8825     cps->offeredDraw = 0;
8826     if (!appData.icsActive) {
8827         SendTimeControl(cps, movesPerSession, timeControl,
8828                         timeIncrement, appData.searchDepth,
8829                         searchTime);
8830     }
8831     if (appData.showThinking 
8832         // [HGM] thinking: four options require thinking output to be sent
8833         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8834                                 ) {
8835         SendToProgram("post\n", cps);
8836     }
8837     SendToProgram("hard\n", cps);
8838     if (!appData.ponderNextMove) {
8839         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8840            it without being sure what state we are in first.  "hard"
8841            is not a toggle, so that one is OK.
8842          */
8843         SendToProgram("easy\n", cps);
8844     }
8845     if (cps->usePing) {
8846       sprintf(buf, "ping %d\n", ++cps->lastPing);
8847       SendToProgram(buf, cps);
8848     }
8849     cps->initDone = TRUE;
8850 }   
8851
8852
8853 void
8854 StartChessProgram(cps)
8855      ChessProgramState *cps;
8856 {
8857     char buf[MSG_SIZ];
8858     int err;
8859
8860     if (appData.noChessProgram) return;
8861     cps->initDone = FALSE;
8862
8863     if (strcmp(cps->host, "localhost") == 0) {
8864         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8865     } else if (*appData.remoteShell == NULLCHAR) {
8866         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8867     } else {
8868         if (*appData.remoteUser == NULLCHAR) {
8869           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8870                     cps->program);
8871         } else {
8872           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8873                     cps->host, appData.remoteUser, cps->program);
8874         }
8875         err = StartChildProcess(buf, "", &cps->pr);
8876     }
8877     
8878     if (err != 0) {
8879         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8880         DisplayFatalError(buf, err, 1);
8881         cps->pr = NoProc;
8882         cps->isr = NULL;
8883         return;
8884     }
8885     
8886     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8887     if (cps->protocolVersion > 1) {
8888       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8889       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8890       cps->comboCnt = 0;  //                and values of combo boxes
8891       SendToProgram(buf, cps);
8892     } else {
8893       SendToProgram("xboard\n", cps);
8894     }
8895 }
8896
8897
8898 void
8899 TwoMachinesEventIfReady P((void))
8900 {
8901   if (first.lastPing != first.lastPong) {
8902     DisplayMessage("", _("Waiting for first chess program"));
8903     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8904     return;
8905   }
8906   if (second.lastPing != second.lastPong) {
8907     DisplayMessage("", _("Waiting for second chess program"));
8908     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8909     return;
8910   }
8911   ThawUI();
8912   TwoMachinesEvent();
8913 }
8914
8915 void
8916 NextMatchGame P((void))
8917 {
8918     int index; /* [HGM] autoinc: step load index during match */
8919     Reset(FALSE, TRUE);
8920     if (*appData.loadGameFile != NULLCHAR) {
8921         index = appData.loadGameIndex;
8922         if(index < 0) { // [HGM] autoinc
8923             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8924             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8925         } 
8926         LoadGameFromFile(appData.loadGameFile,
8927                          index,
8928                          appData.loadGameFile, FALSE);
8929     } else if (*appData.loadPositionFile != NULLCHAR) {
8930         index = appData.loadPositionIndex;
8931         if(index < 0) { // [HGM] autoinc
8932             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8933             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8934         } 
8935         LoadPositionFromFile(appData.loadPositionFile,
8936                              index,
8937                              appData.loadPositionFile);
8938     }
8939     TwoMachinesEventIfReady();
8940 }
8941
8942 void UserAdjudicationEvent( int result )
8943 {
8944     ChessMove gameResult = GameIsDrawn;
8945
8946     if( result > 0 ) {
8947         gameResult = WhiteWins;
8948     }
8949     else if( result < 0 ) {
8950         gameResult = BlackWins;
8951     }
8952
8953     if( gameMode == TwoMachinesPlay ) {
8954         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8955     }
8956 }
8957
8958
8959 // [HGM] save: calculate checksum of game to make games easily identifiable
8960 int StringCheckSum(char *s)
8961 {
8962         int i = 0;
8963         if(s==NULL) return 0;
8964         while(*s) i = i*259 + *s++;
8965         return i;
8966 }
8967
8968 int GameCheckSum()
8969 {
8970         int i, sum=0;
8971         for(i=backwardMostMove; i<forwardMostMove; i++) {
8972                 sum += pvInfoList[i].depth;
8973                 sum += StringCheckSum(parseList[i]);
8974                 sum += StringCheckSum(commentList[i]);
8975                 sum *= 261;
8976         }
8977         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8978         return sum + StringCheckSum(commentList[i]);
8979 } // end of save patch
8980
8981 void
8982 GameEnds(result, resultDetails, whosays)
8983      ChessMove result;
8984      char *resultDetails;
8985      int whosays;
8986 {
8987     GameMode nextGameMode;
8988     int isIcsGame;
8989     char buf[MSG_SIZ], popupRequested = 0;
8990
8991     if(endingGame) return; /* [HGM] crash: forbid recursion */
8992     endingGame = 1;
8993     if(twoBoards) { // [HGM] dual: switch back to one board
8994         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
8995         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
8996     }
8997     if (appData.debugMode) {
8998       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8999               result, resultDetails ? resultDetails : "(null)", whosays);
9000     }
9001
9002     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9003
9004     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9005         /* If we are playing on ICS, the server decides when the
9006            game is over, but the engine can offer to draw, claim 
9007            a draw, or resign. 
9008          */
9009 #if ZIPPY
9010         if (appData.zippyPlay && first.initDone) {
9011             if (result == GameIsDrawn) {
9012                 /* In case draw still needs to be claimed */
9013                 SendToICS(ics_prefix);
9014                 SendToICS("draw\n");
9015             } else if (StrCaseStr(resultDetails, "resign")) {
9016                 SendToICS(ics_prefix);
9017                 SendToICS("resign\n");
9018             }
9019         }
9020 #endif
9021         endingGame = 0; /* [HGM] crash */
9022         return;
9023     }
9024
9025     /* If we're loading the game from a file, stop */
9026     if (whosays == GE_FILE) {
9027       (void) StopLoadGameTimer();
9028       gameFileFP = NULL;
9029     }
9030
9031     /* Cancel draw offers */
9032     first.offeredDraw = second.offeredDraw = 0;
9033
9034     /* If this is an ICS game, only ICS can really say it's done;
9035        if not, anyone can. */
9036     isIcsGame = (gameMode == IcsPlayingWhite || 
9037                  gameMode == IcsPlayingBlack || 
9038                  gameMode == IcsObserving    || 
9039                  gameMode == IcsExamining);
9040
9041     if (!isIcsGame || whosays == GE_ICS) {
9042         /* OK -- not an ICS game, or ICS said it was done */
9043         StopClocks();
9044         if (!isIcsGame && !appData.noChessProgram) 
9045           SetUserThinkingEnables();
9046     
9047         /* [HGM] if a machine claims the game end we verify this claim */
9048         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9049             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9050                 char claimer;
9051                 ChessMove trueResult = (ChessMove) -1;
9052
9053                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9054                                             first.twoMachinesColor[0] :
9055                                             second.twoMachinesColor[0] ;
9056
9057                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9058                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9059                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9060                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9061                 } else
9062                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9063                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9064                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9065                 } else
9066                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9067                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9068                 }
9069
9070                 // now verify win claims, but not in drop games, as we don't understand those yet
9071                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9072                                                  || gameInfo.variant == VariantGreat) &&
9073                     (result == WhiteWins && claimer == 'w' ||
9074                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9075                       if (appData.debugMode) {
9076                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9077                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9078                       }
9079                       if(result != trueResult) {
9080                               sprintf(buf, "False win claim: '%s'", resultDetails);
9081                               result = claimer == 'w' ? BlackWins : WhiteWins;
9082                               resultDetails = buf;
9083                       }
9084                 } else
9085                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9086                     && (forwardMostMove <= backwardMostMove ||
9087                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9088                         (claimer=='b')==(forwardMostMove&1))
9089                                                                                   ) {
9090                       /* [HGM] verify: draws that were not flagged are false claims */
9091                       sprintf(buf, "False draw claim: '%s'", resultDetails);
9092                       result = claimer == 'w' ? BlackWins : WhiteWins;
9093                       resultDetails = buf;
9094                 }
9095                 /* (Claiming a loss is accepted no questions asked!) */
9096             }
9097             /* [HGM] bare: don't allow bare King to win */
9098             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9099                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
9100                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9101                && result != GameIsDrawn)
9102             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9103                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9104                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9105                         if(p >= 0 && p <= (int)WhiteKing) k++;
9106                 }
9107                 if (appData.debugMode) {
9108                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9109                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9110                 }
9111                 if(k <= 1) {
9112                         result = GameIsDrawn;
9113                         sprintf(buf, "%s but bare king", resultDetails);
9114                         resultDetails = buf;
9115                 }
9116             }
9117         }
9118
9119
9120         if(serverMoves != NULL && !loadFlag) { char c = '=';
9121             if(result==WhiteWins) c = '+';
9122             if(result==BlackWins) c = '-';
9123             if(resultDetails != NULL)
9124                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9125         }
9126         if (resultDetails != NULL) {
9127             gameInfo.result = result;
9128             gameInfo.resultDetails = StrSave(resultDetails);
9129
9130             /* display last move only if game was not loaded from file */
9131             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9132                 DisplayMove(currentMove - 1);
9133     
9134             if (forwardMostMove != 0) {
9135                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9136                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9137                                                                 ) {
9138                     if (*appData.saveGameFile != NULLCHAR) {
9139                         SaveGameToFile(appData.saveGameFile, TRUE);
9140                     } else if (appData.autoSaveGames) {
9141                         AutoSaveGame();
9142                     }
9143                     if (*appData.savePositionFile != NULLCHAR) {
9144                         SavePositionToFile(appData.savePositionFile);
9145                     }
9146                 }
9147             }
9148
9149             /* Tell program how game ended in case it is learning */
9150             /* [HGM] Moved this to after saving the PGN, just in case */
9151             /* engine died and we got here through time loss. In that */
9152             /* case we will get a fatal error writing the pipe, which */
9153             /* would otherwise lose us the PGN.                       */
9154             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9155             /* output during GameEnds should never be fatal anymore   */
9156             if (gameMode == MachinePlaysWhite ||
9157                 gameMode == MachinePlaysBlack ||
9158                 gameMode == TwoMachinesPlay ||
9159                 gameMode == IcsPlayingWhite ||
9160                 gameMode == IcsPlayingBlack ||
9161                 gameMode == BeginningOfGame) {
9162                 char buf[MSG_SIZ];
9163                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9164                         resultDetails);
9165                 if (first.pr != NoProc) {
9166                     SendToProgram(buf, &first);
9167                 }
9168                 if (second.pr != NoProc &&
9169                     gameMode == TwoMachinesPlay) {
9170                     SendToProgram(buf, &second);
9171                 }
9172             }
9173         }
9174
9175         if (appData.icsActive) {
9176             if (appData.quietPlay &&
9177                 (gameMode == IcsPlayingWhite ||
9178                  gameMode == IcsPlayingBlack)) {
9179                 SendToICS(ics_prefix);
9180                 SendToICS("set shout 1\n");
9181             }
9182             nextGameMode = IcsIdle;
9183             ics_user_moved = FALSE;
9184             /* clean up premove.  It's ugly when the game has ended and the
9185              * premove highlights are still on the board.
9186              */
9187             if (gotPremove) {
9188               gotPremove = FALSE;
9189               ClearPremoveHighlights();
9190               DrawPosition(FALSE, boards[currentMove]);
9191             }
9192             if (whosays == GE_ICS) {
9193                 switch (result) {
9194                 case WhiteWins:
9195                     if (gameMode == IcsPlayingWhite)
9196                         PlayIcsWinSound();
9197                     else if(gameMode == IcsPlayingBlack)
9198                         PlayIcsLossSound();
9199                     break;
9200                 case BlackWins:
9201                     if (gameMode == IcsPlayingBlack)
9202                         PlayIcsWinSound();
9203                     else if(gameMode == IcsPlayingWhite)
9204                         PlayIcsLossSound();
9205                     break;
9206                 case GameIsDrawn:
9207                     PlayIcsDrawSound();
9208                     break;
9209                 default:
9210                     PlayIcsUnfinishedSound();
9211                 }
9212             }
9213         } else if (gameMode == EditGame ||
9214                    gameMode == PlayFromGameFile || 
9215                    gameMode == AnalyzeMode || 
9216                    gameMode == AnalyzeFile) {
9217             nextGameMode = gameMode;
9218         } else {
9219             nextGameMode = EndOfGame;
9220         }
9221         pausing = FALSE;
9222         ModeHighlight();
9223     } else {
9224         nextGameMode = gameMode;
9225     }
9226
9227     if (appData.noChessProgram) {
9228         gameMode = nextGameMode;
9229         ModeHighlight();
9230         endingGame = 0; /* [HGM] crash */
9231         return;
9232     }
9233
9234     if (first.reuse) {
9235         /* Put first chess program into idle state */
9236         if (first.pr != NoProc &&
9237             (gameMode == MachinePlaysWhite ||
9238              gameMode == MachinePlaysBlack ||
9239              gameMode == TwoMachinesPlay ||
9240              gameMode == IcsPlayingWhite ||
9241              gameMode == IcsPlayingBlack ||
9242              gameMode == BeginningOfGame)) {
9243             SendToProgram("force\n", &first);
9244             if (first.usePing) {
9245               char buf[MSG_SIZ];
9246               sprintf(buf, "ping %d\n", ++first.lastPing);
9247               SendToProgram(buf, &first);
9248             }
9249         }
9250     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9251         /* Kill off first chess program */
9252         if (first.isr != NULL)
9253           RemoveInputSource(first.isr);
9254         first.isr = NULL;
9255     
9256         if (first.pr != NoProc) {
9257             ExitAnalyzeMode();
9258             DoSleep( appData.delayBeforeQuit );
9259             SendToProgram("quit\n", &first);
9260             DoSleep( appData.delayAfterQuit );
9261             DestroyChildProcess(first.pr, first.useSigterm);
9262         }
9263         first.pr = NoProc;
9264     }
9265     if (second.reuse) {
9266         /* Put second chess program into idle state */
9267         if (second.pr != NoProc &&
9268             gameMode == TwoMachinesPlay) {
9269             SendToProgram("force\n", &second);
9270             if (second.usePing) {
9271               char buf[MSG_SIZ];
9272               sprintf(buf, "ping %d\n", ++second.lastPing);
9273               SendToProgram(buf, &second);
9274             }
9275         }
9276     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9277         /* Kill off second chess program */
9278         if (second.isr != NULL)
9279           RemoveInputSource(second.isr);
9280         second.isr = NULL;
9281     
9282         if (second.pr != NoProc) {
9283             DoSleep( appData.delayBeforeQuit );
9284             SendToProgram("quit\n", &second);
9285             DoSleep( appData.delayAfterQuit );
9286             DestroyChildProcess(second.pr, second.useSigterm);
9287         }
9288         second.pr = NoProc;
9289     }
9290
9291     if (matchMode && gameMode == TwoMachinesPlay) {
9292         switch (result) {
9293         case WhiteWins:
9294           if (first.twoMachinesColor[0] == 'w') {
9295             first.matchWins++;
9296           } else {
9297             second.matchWins++;
9298           }
9299           break;
9300         case BlackWins:
9301           if (first.twoMachinesColor[0] == 'b') {
9302             first.matchWins++;
9303           } else {
9304             second.matchWins++;
9305           }
9306           break;
9307         default:
9308           break;
9309         }
9310         if (matchGame < appData.matchGames) {
9311             char *tmp;
9312             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9313                 tmp = first.twoMachinesColor;
9314                 first.twoMachinesColor = second.twoMachinesColor;
9315                 second.twoMachinesColor = tmp;
9316             }
9317             gameMode = nextGameMode;
9318             matchGame++;
9319             if(appData.matchPause>10000 || appData.matchPause<10)
9320                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9321             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9322             endingGame = 0; /* [HGM] crash */
9323             return;
9324         } else {
9325             gameMode = nextGameMode;
9326             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9327                     first.tidy, second.tidy,
9328                     first.matchWins, second.matchWins,
9329                     appData.matchGames - (first.matchWins + second.matchWins));
9330             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9331         }
9332     }
9333     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9334         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9335       ExitAnalyzeMode();
9336     gameMode = nextGameMode;
9337     ModeHighlight();
9338     endingGame = 0;  /* [HGM] crash */
9339     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9340       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9341         matchMode = FALSE; appData.matchGames = matchGame = 0;
9342         DisplayNote(buf);
9343       }
9344     }
9345 }
9346
9347 /* Assumes program was just initialized (initString sent).
9348    Leaves program in force mode. */
9349 void
9350 FeedMovesToProgram(cps, upto) 
9351      ChessProgramState *cps;
9352      int upto;
9353 {
9354     int i;
9355     
9356     if (appData.debugMode)
9357       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9358               startedFromSetupPosition ? "position and " : "",
9359               backwardMostMove, upto, cps->which);
9360     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9361         // [HGM] variantswitch: make engine aware of new variant
9362         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9363                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9364         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9365         SendToProgram(buf, cps);
9366         currentlyInitializedVariant = gameInfo.variant;
9367     }
9368     SendToProgram("force\n", cps);
9369     if (startedFromSetupPosition) {
9370         SendBoard(cps, backwardMostMove);
9371     if (appData.debugMode) {
9372         fprintf(debugFP, "feedMoves\n");
9373     }
9374     }
9375     for (i = backwardMostMove; i < upto; i++) {
9376         SendMoveToProgram(i, cps);
9377     }
9378 }
9379
9380
9381 void
9382 ResurrectChessProgram()
9383 {
9384      /* The chess program may have exited.
9385         If so, restart it and feed it all the moves made so far. */
9386
9387     if (appData.noChessProgram || first.pr != NoProc) return;
9388     
9389     StartChessProgram(&first);
9390     InitChessProgram(&first, FALSE);
9391     FeedMovesToProgram(&first, currentMove);
9392
9393     if (!first.sendTime) {
9394         /* can't tell gnuchess what its clock should read,
9395            so we bow to its notion. */
9396         ResetClocks();
9397         timeRemaining[0][currentMove] = whiteTimeRemaining;
9398         timeRemaining[1][currentMove] = blackTimeRemaining;
9399     }
9400
9401     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9402                 appData.icsEngineAnalyze) && first.analysisSupport) {
9403       SendToProgram("analyze\n", &first);
9404       first.analyzing = TRUE;
9405     }
9406 }
9407
9408 /*
9409  * Button procedures
9410  */
9411 void
9412 Reset(redraw, init)
9413      int redraw, init;
9414 {
9415     int i;
9416
9417     if (appData.debugMode) {
9418         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9419                 redraw, init, gameMode);
9420     }
9421     CleanupTail(); // [HGM] vari: delete any stored variations
9422     pausing = pauseExamInvalid = FALSE;
9423     startedFromSetupPosition = blackPlaysFirst = FALSE;
9424     firstMove = TRUE;
9425     whiteFlag = blackFlag = FALSE;
9426     userOfferedDraw = FALSE;
9427     hintRequested = bookRequested = FALSE;
9428     first.maybeThinking = FALSE;
9429     second.maybeThinking = FALSE;
9430     first.bookSuspend = FALSE; // [HGM] book
9431     second.bookSuspend = FALSE;
9432     thinkOutput[0] = NULLCHAR;
9433     lastHint[0] = NULLCHAR;
9434     ClearGameInfo(&gameInfo);
9435     gameInfo.variant = StringToVariant(appData.variant);
9436     ics_user_moved = ics_clock_paused = FALSE;
9437     ics_getting_history = H_FALSE;
9438     ics_gamenum = -1;
9439     white_holding[0] = black_holding[0] = NULLCHAR;
9440     ClearProgramStats();
9441     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9442     
9443     ResetFrontEnd();
9444     ClearHighlights();
9445     flipView = appData.flipView;
9446     ClearPremoveHighlights();
9447     gotPremove = FALSE;
9448     alarmSounded = FALSE;
9449
9450     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9451     if(appData.serverMovesName != NULL) {
9452         /* [HGM] prepare to make moves file for broadcasting */
9453         clock_t t = clock();
9454         if(serverMoves != NULL) fclose(serverMoves);
9455         serverMoves = fopen(appData.serverMovesName, "r");
9456         if(serverMoves != NULL) {
9457             fclose(serverMoves);
9458             /* delay 15 sec before overwriting, so all clients can see end */
9459             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9460         }
9461         serverMoves = fopen(appData.serverMovesName, "w");
9462     }
9463
9464     ExitAnalyzeMode();
9465     gameMode = BeginningOfGame;
9466     ModeHighlight();
9467     if(appData.icsActive) gameInfo.variant = VariantNormal;
9468     currentMove = forwardMostMove = backwardMostMove = 0;
9469     InitPosition(redraw);
9470     for (i = 0; i < MAX_MOVES; i++) {
9471         if (commentList[i] != NULL) {
9472             free(commentList[i]);
9473             commentList[i] = NULL;
9474         }
9475     }
9476     ResetClocks();
9477     timeRemaining[0][0] = whiteTimeRemaining;
9478     timeRemaining[1][0] = blackTimeRemaining;
9479     if (first.pr == NULL) {
9480         StartChessProgram(&first);
9481     }
9482     if (init) {
9483             InitChessProgram(&first, startedFromSetupPosition);
9484     }
9485     DisplayTitle("");
9486     DisplayMessage("", "");
9487     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9488     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9489 }
9490
9491 void
9492 AutoPlayGameLoop()
9493 {
9494     for (;;) {
9495         if (!AutoPlayOneMove())
9496           return;
9497         if (matchMode || appData.timeDelay == 0)
9498           continue;
9499         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9500           return;
9501         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9502         break;
9503     }
9504 }
9505
9506
9507 int
9508 AutoPlayOneMove()
9509 {
9510     int fromX, fromY, toX, toY;
9511
9512     if (appData.debugMode) {
9513       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9514     }
9515
9516     if (gameMode != PlayFromGameFile)
9517       return FALSE;
9518
9519     if (currentMove >= forwardMostMove) {
9520       gameMode = EditGame;
9521       ModeHighlight();
9522
9523       /* [AS] Clear current move marker at the end of a game */
9524       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9525
9526       return FALSE;
9527     }
9528     
9529     toX = moveList[currentMove][2] - AAA;
9530     toY = moveList[currentMove][3] - ONE;
9531
9532     if (moveList[currentMove][1] == '@') {
9533         if (appData.highlightLastMove) {
9534             SetHighlights(-1, -1, toX, toY);
9535         }
9536     } else {
9537         fromX = moveList[currentMove][0] - AAA;
9538         fromY = moveList[currentMove][1] - ONE;
9539
9540         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9541
9542         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9543
9544         if (appData.highlightLastMove) {
9545             SetHighlights(fromX, fromY, toX, toY);
9546         }
9547     }
9548     DisplayMove(currentMove);
9549     SendMoveToProgram(currentMove++, &first);
9550     DisplayBothClocks();
9551     DrawPosition(FALSE, boards[currentMove]);
9552     // [HGM] PV info: always display, routine tests if empty
9553     DisplayComment(currentMove - 1, commentList[currentMove]);
9554     return TRUE;
9555 }
9556
9557
9558 int
9559 LoadGameOneMove(readAhead)
9560      ChessMove readAhead;
9561 {
9562     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9563     char promoChar = NULLCHAR;
9564     ChessMove moveType;
9565     char move[MSG_SIZ];
9566     char *p, *q;
9567     
9568     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9569         gameMode != AnalyzeMode && gameMode != Training) {
9570         gameFileFP = NULL;
9571         return FALSE;
9572     }
9573     
9574     yyboardindex = forwardMostMove;
9575     if (readAhead != (ChessMove)0) {
9576       moveType = readAhead;
9577     } else {
9578       if (gameFileFP == NULL)
9579           return FALSE;
9580       moveType = (ChessMove) yylex();
9581     }
9582     
9583     done = FALSE;
9584     switch (moveType) {
9585       case Comment:
9586         if (appData.debugMode) 
9587           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9588         p = yy_text;
9589
9590         /* append the comment but don't display it */
9591         AppendComment(currentMove, p, FALSE);
9592         return TRUE;
9593
9594       case WhiteCapturesEnPassant:
9595       case BlackCapturesEnPassant:
9596       case WhitePromotionChancellor:
9597       case BlackPromotionChancellor:
9598       case WhitePromotionArchbishop:
9599       case BlackPromotionArchbishop:
9600       case WhitePromotionCentaur:
9601       case BlackPromotionCentaur:
9602       case WhitePromotionQueen:
9603       case BlackPromotionQueen:
9604       case WhitePromotionRook:
9605       case BlackPromotionRook:
9606       case WhitePromotionBishop:
9607       case BlackPromotionBishop:
9608       case WhitePromotionKnight:
9609       case BlackPromotionKnight:
9610       case WhitePromotionKing:
9611       case BlackPromotionKing:
9612       case WhiteNonPromotion:
9613       case BlackNonPromotion:
9614       case NormalMove:
9615       case WhiteKingSideCastle:
9616       case WhiteQueenSideCastle:
9617       case BlackKingSideCastle:
9618       case BlackQueenSideCastle:
9619       case WhiteKingSideCastleWild:
9620       case WhiteQueenSideCastleWild:
9621       case BlackKingSideCastleWild:
9622       case BlackQueenSideCastleWild:
9623       /* PUSH Fabien */
9624       case WhiteHSideCastleFR:
9625       case WhiteASideCastleFR:
9626       case BlackHSideCastleFR:
9627       case BlackASideCastleFR:
9628       /* POP Fabien */
9629         if (appData.debugMode)
9630           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9631         fromX = currentMoveString[0] - AAA;
9632         fromY = currentMoveString[1] - ONE;
9633         toX = currentMoveString[2] - AAA;
9634         toY = currentMoveString[3] - ONE;
9635         promoChar = currentMoveString[4];
9636         break;
9637
9638       case WhiteDrop:
9639       case BlackDrop:
9640         if (appData.debugMode)
9641           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9642         fromX = moveType == WhiteDrop ?
9643           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9644         (int) CharToPiece(ToLower(currentMoveString[0]));
9645         fromY = DROP_RANK;
9646         toX = currentMoveString[2] - AAA;
9647         toY = currentMoveString[3] - ONE;
9648         break;
9649
9650       case WhiteWins:
9651       case BlackWins:
9652       case GameIsDrawn:
9653       case GameUnfinished:
9654         if (appData.debugMode)
9655           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9656         p = strchr(yy_text, '{');
9657         if (p == NULL) p = strchr(yy_text, '(');
9658         if (p == NULL) {
9659             p = yy_text;
9660             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9661         } else {
9662             q = strchr(p, *p == '{' ? '}' : ')');
9663             if (q != NULL) *q = NULLCHAR;
9664             p++;
9665         }
9666         GameEnds(moveType, p, GE_FILE);
9667         done = TRUE;
9668         if (cmailMsgLoaded) {
9669             ClearHighlights();
9670             flipView = WhiteOnMove(currentMove);
9671             if (moveType == GameUnfinished) flipView = !flipView;
9672             if (appData.debugMode)
9673               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9674         }
9675         break;
9676
9677       case (ChessMove) 0:       /* end of file */
9678         if (appData.debugMode)
9679           fprintf(debugFP, "Parser hit end of file\n");
9680         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9681           case MT_NONE:
9682           case MT_CHECK:
9683             break;
9684           case MT_CHECKMATE:
9685           case MT_STAINMATE:
9686             if (WhiteOnMove(currentMove)) {
9687                 GameEnds(BlackWins, "Black mates", GE_FILE);
9688             } else {
9689                 GameEnds(WhiteWins, "White mates", GE_FILE);
9690             }
9691             break;
9692           case MT_STALEMATE:
9693             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9694             break;
9695         }
9696         done = TRUE;
9697         break;
9698
9699       case MoveNumberOne:
9700         if (lastLoadGameStart == GNUChessGame) {
9701             /* GNUChessGames have numbers, but they aren't move numbers */
9702             if (appData.debugMode)
9703               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9704                       yy_text, (int) moveType);
9705             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9706         }
9707         /* else fall thru */
9708
9709       case XBoardGame:
9710       case GNUChessGame:
9711       case PGNTag:
9712         /* Reached start of next game in file */
9713         if (appData.debugMode)
9714           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9715         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9716           case MT_NONE:
9717           case MT_CHECK:
9718             break;
9719           case MT_CHECKMATE:
9720           case MT_STAINMATE:
9721             if (WhiteOnMove(currentMove)) {
9722                 GameEnds(BlackWins, "Black mates", GE_FILE);
9723             } else {
9724                 GameEnds(WhiteWins, "White mates", GE_FILE);
9725             }
9726             break;
9727           case MT_STALEMATE:
9728             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9729             break;
9730         }
9731         done = TRUE;
9732         break;
9733
9734       case PositionDiagram:     /* should not happen; ignore */
9735       case ElapsedTime:         /* ignore */
9736       case NAG:                 /* ignore */
9737         if (appData.debugMode)
9738           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9739                   yy_text, (int) moveType);
9740         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9741
9742       case IllegalMove:
9743         if (appData.testLegality) {
9744             if (appData.debugMode)
9745               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9746             sprintf(move, _("Illegal move: %d.%s%s"),
9747                     (forwardMostMove / 2) + 1,
9748                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9749             DisplayError(move, 0);
9750             done = TRUE;
9751         } else {
9752             if (appData.debugMode)
9753               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9754                       yy_text, currentMoveString);
9755             fromX = currentMoveString[0] - AAA;
9756             fromY = currentMoveString[1] - ONE;
9757             toX = currentMoveString[2] - AAA;
9758             toY = currentMoveString[3] - ONE;
9759             promoChar = currentMoveString[4];
9760         }
9761         break;
9762
9763       case AmbiguousMove:
9764         if (appData.debugMode)
9765           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9766         sprintf(move, _("Ambiguous move: %d.%s%s"),
9767                 (forwardMostMove / 2) + 1,
9768                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9769         DisplayError(move, 0);
9770         done = TRUE;
9771         break;
9772
9773       default:
9774       case ImpossibleMove:
9775         if (appData.debugMode)
9776           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9777         sprintf(move, _("Illegal move: %d.%s%s"),
9778                 (forwardMostMove / 2) + 1,
9779                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9780         DisplayError(move, 0);
9781         done = TRUE;
9782         break;
9783     }
9784
9785     if (done) {
9786         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9787             DrawPosition(FALSE, boards[currentMove]);
9788             DisplayBothClocks();
9789             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9790               DisplayComment(currentMove - 1, commentList[currentMove]);
9791         }
9792         (void) StopLoadGameTimer();
9793         gameFileFP = NULL;
9794         cmailOldMove = forwardMostMove;
9795         return FALSE;
9796     } else {
9797         /* currentMoveString is set as a side-effect of yylex */
9798         strcat(currentMoveString, "\n");
9799         strcpy(moveList[forwardMostMove], currentMoveString);
9800         
9801         thinkOutput[0] = NULLCHAR;
9802         MakeMove(fromX, fromY, toX, toY, promoChar);
9803         currentMove = forwardMostMove;
9804         return TRUE;
9805     }
9806 }
9807
9808 /* Load the nth game from the given file */
9809 int
9810 LoadGameFromFile(filename, n, title, useList)
9811      char *filename;
9812      int n;
9813      char *title;
9814      /*Boolean*/ int useList;
9815 {
9816     FILE *f;
9817     char buf[MSG_SIZ];
9818
9819     if (strcmp(filename, "-") == 0) {
9820         f = stdin;
9821         title = "stdin";
9822     } else {
9823         f = fopen(filename, "rb");
9824         if (f == NULL) {
9825           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9826             DisplayError(buf, errno);
9827             return FALSE;
9828         }
9829     }
9830     if (fseek(f, 0, 0) == -1) {
9831         /* f is not seekable; probably a pipe */
9832         useList = FALSE;
9833     }
9834     if (useList && n == 0) {
9835         int error = GameListBuild(f);
9836         if (error) {
9837             DisplayError(_("Cannot build game list"), error);
9838         } else if (!ListEmpty(&gameList) &&
9839                    ((ListGame *) gameList.tailPred)->number > 1) {
9840             GameListPopUp(f, title);
9841             return TRUE;
9842         }
9843         GameListDestroy();
9844         n = 1;
9845     }
9846     if (n == 0) n = 1;
9847     return LoadGame(f, n, title, FALSE);
9848 }
9849
9850
9851 void
9852 MakeRegisteredMove()
9853 {
9854     int fromX, fromY, toX, toY;
9855     char promoChar;
9856     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9857         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9858           case CMAIL_MOVE:
9859           case CMAIL_DRAW:
9860             if (appData.debugMode)
9861               fprintf(debugFP, "Restoring %s for game %d\n",
9862                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9863     
9864             thinkOutput[0] = NULLCHAR;
9865             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9866             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9867             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9868             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9869             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9870             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9871             MakeMove(fromX, fromY, toX, toY, promoChar);
9872             ShowMove(fromX, fromY, toX, toY);
9873               
9874             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9875               case MT_NONE:
9876               case MT_CHECK:
9877                 break;
9878                 
9879               case MT_CHECKMATE:
9880               case MT_STAINMATE:
9881                 if (WhiteOnMove(currentMove)) {
9882                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9883                 } else {
9884                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9885                 }
9886                 break;
9887                 
9888               case MT_STALEMATE:
9889                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9890                 break;
9891             }
9892
9893             break;
9894             
9895           case CMAIL_RESIGN:
9896             if (WhiteOnMove(currentMove)) {
9897                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9898             } else {
9899                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9900             }
9901             break;
9902             
9903           case CMAIL_ACCEPT:
9904             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9905             break;
9906               
9907           default:
9908             break;
9909         }
9910     }
9911
9912     return;
9913 }
9914
9915 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9916 int
9917 CmailLoadGame(f, gameNumber, title, useList)
9918      FILE *f;
9919      int gameNumber;
9920      char *title;
9921      int useList;
9922 {
9923     int retVal;
9924
9925     if (gameNumber > nCmailGames) {
9926         DisplayError(_("No more games in this message"), 0);
9927         return FALSE;
9928     }
9929     if (f == lastLoadGameFP) {
9930         int offset = gameNumber - lastLoadGameNumber;
9931         if (offset == 0) {
9932             cmailMsg[0] = NULLCHAR;
9933             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9934                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9935                 nCmailMovesRegistered--;
9936             }
9937             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9938             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9939                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9940             }
9941         } else {
9942             if (! RegisterMove()) return FALSE;
9943         }
9944     }
9945
9946     retVal = LoadGame(f, gameNumber, title, useList);
9947
9948     /* Make move registered during previous look at this game, if any */
9949     MakeRegisteredMove();
9950
9951     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9952         commentList[currentMove]
9953           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9954         DisplayComment(currentMove - 1, commentList[currentMove]);
9955     }
9956
9957     return retVal;
9958 }
9959
9960 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9961 int
9962 ReloadGame(offset)
9963      int offset;
9964 {
9965     int gameNumber = lastLoadGameNumber + offset;
9966     if (lastLoadGameFP == NULL) {
9967         DisplayError(_("No game has been loaded yet"), 0);
9968         return FALSE;
9969     }
9970     if (gameNumber <= 0) {
9971         DisplayError(_("Can't back up any further"), 0);
9972         return FALSE;
9973     }
9974     if (cmailMsgLoaded) {
9975         return CmailLoadGame(lastLoadGameFP, gameNumber,
9976                              lastLoadGameTitle, lastLoadGameUseList);
9977     } else {
9978         return LoadGame(lastLoadGameFP, gameNumber,
9979                         lastLoadGameTitle, lastLoadGameUseList);
9980     }
9981 }
9982
9983
9984
9985 /* Load the nth game from open file f */
9986 int
9987 LoadGame(f, gameNumber, title, useList)
9988      FILE *f;
9989      int gameNumber;
9990      char *title;
9991      int useList;
9992 {
9993     ChessMove cm;
9994     char buf[MSG_SIZ];
9995     int gn = gameNumber;
9996     ListGame *lg = NULL;
9997     int numPGNTags = 0;
9998     int err;
9999     GameMode oldGameMode;
10000     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10001
10002     if (appData.debugMode) 
10003         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10004
10005     if (gameMode == Training )
10006         SetTrainingModeOff();
10007
10008     oldGameMode = gameMode;
10009     if (gameMode != BeginningOfGame) {
10010       Reset(FALSE, TRUE);
10011     }
10012
10013     gameFileFP = f;
10014     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10015         fclose(lastLoadGameFP);
10016     }
10017
10018     if (useList) {
10019         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10020         
10021         if (lg) {
10022             fseek(f, lg->offset, 0);
10023             GameListHighlight(gameNumber);
10024             gn = 1;
10025         }
10026         else {
10027             DisplayError(_("Game number out of range"), 0);
10028             return FALSE;
10029         }
10030     } else {
10031         GameListDestroy();
10032         if (fseek(f, 0, 0) == -1) {
10033             if (f == lastLoadGameFP ?
10034                 gameNumber == lastLoadGameNumber + 1 :
10035                 gameNumber == 1) {
10036                 gn = 1;
10037             } else {
10038                 DisplayError(_("Can't seek on game file"), 0);
10039                 return FALSE;
10040             }
10041         }
10042     }
10043     lastLoadGameFP = f;
10044     lastLoadGameNumber = gameNumber;
10045     strcpy(lastLoadGameTitle, title);
10046     lastLoadGameUseList = useList;
10047
10048     yynewfile(f);
10049
10050     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10051       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10052                 lg->gameInfo.black);
10053             DisplayTitle(buf);
10054     } else if (*title != NULLCHAR) {
10055         if (gameNumber > 1) {
10056             sprintf(buf, "%s %d", title, gameNumber);
10057             DisplayTitle(buf);
10058         } else {
10059             DisplayTitle(title);
10060         }
10061     }
10062
10063     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10064         gameMode = PlayFromGameFile;
10065         ModeHighlight();
10066     }
10067
10068     currentMove = forwardMostMove = backwardMostMove = 0;
10069     CopyBoard(boards[0], initialPosition);
10070     StopClocks();
10071
10072     /*
10073      * Skip the first gn-1 games in the file.
10074      * Also skip over anything that precedes an identifiable 
10075      * start of game marker, to avoid being confused by 
10076      * garbage at the start of the file.  Currently 
10077      * recognized start of game markers are the move number "1",
10078      * the pattern "gnuchess .* game", the pattern
10079      * "^[#;%] [^ ]* game file", and a PGN tag block.  
10080      * A game that starts with one of the latter two patterns
10081      * will also have a move number 1, possibly
10082      * following a position diagram.
10083      * 5-4-02: Let's try being more lenient and allowing a game to
10084      * start with an unnumbered move.  Does that break anything?
10085      */
10086     cm = lastLoadGameStart = (ChessMove) 0;
10087     while (gn > 0) {
10088         yyboardindex = forwardMostMove;
10089         cm = (ChessMove) yylex();
10090         switch (cm) {
10091           case (ChessMove) 0:
10092             if (cmailMsgLoaded) {
10093                 nCmailGames = CMAIL_MAX_GAMES - gn;
10094             } else {
10095                 Reset(TRUE, TRUE);
10096                 DisplayError(_("Game not found in file"), 0);
10097             }
10098             return FALSE;
10099
10100           case GNUChessGame:
10101           case XBoardGame:
10102             gn--;
10103             lastLoadGameStart = cm;
10104             break;
10105             
10106           case MoveNumberOne:
10107             switch (lastLoadGameStart) {
10108               case GNUChessGame:
10109               case XBoardGame:
10110               case PGNTag:
10111                 break;
10112               case MoveNumberOne:
10113               case (ChessMove) 0:
10114                 gn--;           /* count this game */
10115                 lastLoadGameStart = cm;
10116                 break;
10117               default:
10118                 /* impossible */
10119                 break;
10120             }
10121             break;
10122
10123           case PGNTag:
10124             switch (lastLoadGameStart) {
10125               case GNUChessGame:
10126               case PGNTag:
10127               case MoveNumberOne:
10128               case (ChessMove) 0:
10129                 gn--;           /* count this game */
10130                 lastLoadGameStart = cm;
10131                 break;
10132               case XBoardGame:
10133                 lastLoadGameStart = cm; /* game counted already */
10134                 break;
10135               default:
10136                 /* impossible */
10137                 break;
10138             }
10139             if (gn > 0) {
10140                 do {
10141                     yyboardindex = forwardMostMove;
10142                     cm = (ChessMove) yylex();
10143                 } while (cm == PGNTag || cm == Comment);
10144             }
10145             break;
10146
10147           case WhiteWins:
10148           case BlackWins:
10149           case GameIsDrawn:
10150             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10151                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10152                     != CMAIL_OLD_RESULT) {
10153                     nCmailResults ++ ;
10154                     cmailResult[  CMAIL_MAX_GAMES
10155                                 - gn - 1] = CMAIL_OLD_RESULT;
10156                 }
10157             }
10158             break;
10159
10160           case NormalMove:
10161             /* Only a NormalMove can be at the start of a game
10162              * without a position diagram. */
10163             if (lastLoadGameStart == (ChessMove) 0) {
10164               gn--;
10165               lastLoadGameStart = MoveNumberOne;
10166             }
10167             break;
10168
10169           default:
10170             break;
10171         }
10172     }
10173     
10174     if (appData.debugMode)
10175       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10176
10177     if (cm == XBoardGame) {
10178         /* Skip any header junk before position diagram and/or move 1 */
10179         for (;;) {
10180             yyboardindex = forwardMostMove;
10181             cm = (ChessMove) yylex();
10182
10183             if (cm == (ChessMove) 0 ||
10184                 cm == GNUChessGame || cm == XBoardGame) {
10185                 /* Empty game; pretend end-of-file and handle later */
10186                 cm = (ChessMove) 0;
10187                 break;
10188             }
10189
10190             if (cm == MoveNumberOne || cm == PositionDiagram ||
10191                 cm == PGNTag || cm == Comment)
10192               break;
10193         }
10194     } else if (cm == GNUChessGame) {
10195         if (gameInfo.event != NULL) {
10196             free(gameInfo.event);
10197         }
10198         gameInfo.event = StrSave(yy_text);
10199     }   
10200
10201     startedFromSetupPosition = FALSE;
10202     while (cm == PGNTag) {
10203         if (appData.debugMode) 
10204           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10205         err = ParsePGNTag(yy_text, &gameInfo);
10206         if (!err) numPGNTags++;
10207
10208         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10209         if(gameInfo.variant != oldVariant) {
10210             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10211             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10212             InitPosition(TRUE);
10213             oldVariant = gameInfo.variant;
10214             if (appData.debugMode) 
10215               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10216         }
10217
10218
10219         if (gameInfo.fen != NULL) {
10220           Board initial_position;
10221           startedFromSetupPosition = TRUE;
10222           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10223             Reset(TRUE, TRUE);
10224             DisplayError(_("Bad FEN position in file"), 0);
10225             return FALSE;
10226           }
10227           CopyBoard(boards[0], initial_position);
10228           if (blackPlaysFirst) {
10229             currentMove = forwardMostMove = backwardMostMove = 1;
10230             CopyBoard(boards[1], initial_position);
10231             strcpy(moveList[0], "");
10232             strcpy(parseList[0], "");
10233             timeRemaining[0][1] = whiteTimeRemaining;
10234             timeRemaining[1][1] = blackTimeRemaining;
10235             if (commentList[0] != NULL) {
10236               commentList[1] = commentList[0];
10237               commentList[0] = NULL;
10238             }
10239           } else {
10240             currentMove = forwardMostMove = backwardMostMove = 0;
10241           }
10242           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10243           {   int i;
10244               initialRulePlies = FENrulePlies;
10245               for( i=0; i< nrCastlingRights; i++ )
10246                   initialRights[i] = initial_position[CASTLING][i];
10247           }
10248           yyboardindex = forwardMostMove;
10249           free(gameInfo.fen);
10250           gameInfo.fen = NULL;
10251         }
10252
10253         yyboardindex = forwardMostMove;
10254         cm = (ChessMove) yylex();
10255
10256         /* Handle comments interspersed among the tags */
10257         while (cm == Comment) {
10258             char *p;
10259             if (appData.debugMode) 
10260               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10261             p = yy_text;
10262             AppendComment(currentMove, p, FALSE);
10263             yyboardindex = forwardMostMove;
10264             cm = (ChessMove) yylex();
10265         }
10266     }
10267
10268     /* don't rely on existence of Event tag since if game was
10269      * pasted from clipboard the Event tag may not exist
10270      */
10271     if (numPGNTags > 0){
10272         char *tags;
10273         if (gameInfo.variant == VariantNormal) {
10274           VariantClass v = StringToVariant(gameInfo.event);
10275           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10276           if(v < VariantShogi) gameInfo.variant = v;
10277         }
10278         if (!matchMode) {
10279           if( appData.autoDisplayTags ) {
10280             tags = PGNTags(&gameInfo);
10281             TagsPopUp(tags, CmailMsg());
10282             free(tags);
10283           }
10284         }
10285     } else {
10286         /* Make something up, but don't display it now */
10287         SetGameInfo();
10288         TagsPopDown();
10289     }
10290
10291     if (cm == PositionDiagram) {
10292         int i, j;
10293         char *p;
10294         Board initial_position;
10295
10296         if (appData.debugMode)
10297           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10298
10299         if (!startedFromSetupPosition) {
10300             p = yy_text;
10301             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10302               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10303                 switch (*p) {
10304                   case '[':
10305                   case '-':
10306                   case ' ':
10307                   case '\t':
10308                   case '\n':
10309                   case '\r':
10310                     break;
10311                   default:
10312                     initial_position[i][j++] = CharToPiece(*p);
10313                     break;
10314                 }
10315             while (*p == ' ' || *p == '\t' ||
10316                    *p == '\n' || *p == '\r') p++;
10317         
10318             if (strncmp(p, "black", strlen("black"))==0)
10319               blackPlaysFirst = TRUE;
10320             else
10321               blackPlaysFirst = FALSE;
10322             startedFromSetupPosition = TRUE;
10323         
10324             CopyBoard(boards[0], initial_position);
10325             if (blackPlaysFirst) {
10326                 currentMove = forwardMostMove = backwardMostMove = 1;
10327                 CopyBoard(boards[1], initial_position);
10328                 strcpy(moveList[0], "");
10329                 strcpy(parseList[0], "");
10330                 timeRemaining[0][1] = whiteTimeRemaining;
10331                 timeRemaining[1][1] = blackTimeRemaining;
10332                 if (commentList[0] != NULL) {
10333                     commentList[1] = commentList[0];
10334                     commentList[0] = NULL;
10335                 }
10336             } else {
10337                 currentMove = forwardMostMove = backwardMostMove = 0;
10338             }
10339         }
10340         yyboardindex = forwardMostMove;
10341         cm = (ChessMove) yylex();
10342     }
10343
10344     if (first.pr == NoProc) {
10345         StartChessProgram(&first);
10346     }
10347     InitChessProgram(&first, FALSE);
10348     SendToProgram("force\n", &first);
10349     if (startedFromSetupPosition) {
10350         SendBoard(&first, forwardMostMove);
10351     if (appData.debugMode) {
10352         fprintf(debugFP, "Load Game\n");
10353     }
10354         DisplayBothClocks();
10355     }      
10356
10357     /* [HGM] server: flag to write setup moves in broadcast file as one */
10358     loadFlag = appData.suppressLoadMoves;
10359
10360     while (cm == Comment) {
10361         char *p;
10362         if (appData.debugMode) 
10363           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10364         p = yy_text;
10365         AppendComment(currentMove, p, FALSE);
10366         yyboardindex = forwardMostMove;
10367         cm = (ChessMove) yylex();
10368     }
10369
10370     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10371         cm == WhiteWins || cm == BlackWins ||
10372         cm == GameIsDrawn || cm == GameUnfinished) {
10373         DisplayMessage("", _("No moves in game"));
10374         if (cmailMsgLoaded) {
10375             if (appData.debugMode)
10376               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10377             ClearHighlights();
10378             flipView = FALSE;
10379         }
10380         DrawPosition(FALSE, boards[currentMove]);
10381         DisplayBothClocks();
10382         gameMode = EditGame;
10383         ModeHighlight();
10384         gameFileFP = NULL;
10385         cmailOldMove = 0;
10386         return TRUE;
10387     }
10388
10389     // [HGM] PV info: routine tests if comment empty
10390     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10391         DisplayComment(currentMove - 1, commentList[currentMove]);
10392     }
10393     if (!matchMode && appData.timeDelay != 0) 
10394       DrawPosition(FALSE, boards[currentMove]);
10395
10396     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10397       programStats.ok_to_send = 1;
10398     }
10399
10400     /* if the first token after the PGN tags is a move
10401      * and not move number 1, retrieve it from the parser 
10402      */
10403     if (cm != MoveNumberOne)
10404         LoadGameOneMove(cm);
10405
10406     /* load the remaining moves from the file */
10407     while (LoadGameOneMove((ChessMove)0)) {
10408       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10409       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10410     }
10411
10412     /* rewind to the start of the game */
10413     currentMove = backwardMostMove;
10414
10415     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10416
10417     if (oldGameMode == AnalyzeFile ||
10418         oldGameMode == AnalyzeMode) {
10419       AnalyzeFileEvent();
10420     }
10421
10422     if (matchMode || appData.timeDelay == 0) {
10423       ToEndEvent();
10424       gameMode = EditGame;
10425       ModeHighlight();
10426     } else if (appData.timeDelay > 0) {
10427       AutoPlayGameLoop();
10428     }
10429
10430     if (appData.debugMode) 
10431         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10432
10433     loadFlag = 0; /* [HGM] true game starts */
10434     return TRUE;
10435 }
10436
10437 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10438 int
10439 ReloadPosition(offset)
10440      int offset;
10441 {
10442     int positionNumber = lastLoadPositionNumber + offset;
10443     if (lastLoadPositionFP == NULL) {
10444         DisplayError(_("No position has been loaded yet"), 0);
10445         return FALSE;
10446     }
10447     if (positionNumber <= 0) {
10448         DisplayError(_("Can't back up any further"), 0);
10449         return FALSE;
10450     }
10451     return LoadPosition(lastLoadPositionFP, positionNumber,
10452                         lastLoadPositionTitle);
10453 }
10454
10455 /* Load the nth position from the given file */
10456 int
10457 LoadPositionFromFile(filename, n, title)
10458      char *filename;
10459      int n;
10460      char *title;
10461 {
10462     FILE *f;
10463     char buf[MSG_SIZ];
10464
10465     if (strcmp(filename, "-") == 0) {
10466         return LoadPosition(stdin, n, "stdin");
10467     } else {
10468         f = fopen(filename, "rb");
10469         if (f == NULL) {
10470             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10471             DisplayError(buf, errno);
10472             return FALSE;
10473         } else {
10474             return LoadPosition(f, n, title);
10475         }
10476     }
10477 }
10478
10479 /* Load the nth position from the given open file, and close it */
10480 int
10481 LoadPosition(f, positionNumber, title)
10482      FILE *f;
10483      int positionNumber;
10484      char *title;
10485 {
10486     char *p, line[MSG_SIZ];
10487     Board initial_position;
10488     int i, j, fenMode, pn;
10489     
10490     if (gameMode == Training )
10491         SetTrainingModeOff();
10492
10493     if (gameMode != BeginningOfGame) {
10494         Reset(FALSE, TRUE);
10495     }
10496     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10497         fclose(lastLoadPositionFP);
10498     }
10499     if (positionNumber == 0) positionNumber = 1;
10500     lastLoadPositionFP = f;
10501     lastLoadPositionNumber = positionNumber;
10502     strcpy(lastLoadPositionTitle, title);
10503     if (first.pr == NoProc) {
10504       StartChessProgram(&first);
10505       InitChessProgram(&first, FALSE);
10506     }    
10507     pn = positionNumber;
10508     if (positionNumber < 0) {
10509         /* Negative position number means to seek to that byte offset */
10510         if (fseek(f, -positionNumber, 0) == -1) {
10511             DisplayError(_("Can't seek on position file"), 0);
10512             return FALSE;
10513         };
10514         pn = 1;
10515     } else {
10516         if (fseek(f, 0, 0) == -1) {
10517             if (f == lastLoadPositionFP ?
10518                 positionNumber == lastLoadPositionNumber + 1 :
10519                 positionNumber == 1) {
10520                 pn = 1;
10521             } else {
10522                 DisplayError(_("Can't seek on position file"), 0);
10523                 return FALSE;
10524             }
10525         }
10526     }
10527     /* See if this file is FEN or old-style xboard */
10528     if (fgets(line, MSG_SIZ, f) == NULL) {
10529         DisplayError(_("Position not found in file"), 0);
10530         return FALSE;
10531     }
10532     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10533     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10534
10535     if (pn >= 2) {
10536         if (fenMode || line[0] == '#') pn--;
10537         while (pn > 0) {
10538             /* skip positions before number pn */
10539             if (fgets(line, MSG_SIZ, f) == NULL) {
10540                 Reset(TRUE, TRUE);
10541                 DisplayError(_("Position not found in file"), 0);
10542                 return FALSE;
10543             }
10544             if (fenMode || line[0] == '#') pn--;
10545         }
10546     }
10547
10548     if (fenMode) {
10549         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10550             DisplayError(_("Bad FEN position in file"), 0);
10551             return FALSE;
10552         }
10553     } else {
10554         (void) fgets(line, MSG_SIZ, f);
10555         (void) fgets(line, MSG_SIZ, f);
10556     
10557         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10558             (void) fgets(line, MSG_SIZ, f);
10559             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10560                 if (*p == ' ')
10561                   continue;
10562                 initial_position[i][j++] = CharToPiece(*p);
10563             }
10564         }
10565     
10566         blackPlaysFirst = FALSE;
10567         if (!feof(f)) {
10568             (void) fgets(line, MSG_SIZ, f);
10569             if (strncmp(line, "black", strlen("black"))==0)
10570               blackPlaysFirst = TRUE;
10571         }
10572     }
10573     startedFromSetupPosition = TRUE;
10574     
10575     SendToProgram("force\n", &first);
10576     CopyBoard(boards[0], initial_position);
10577     if (blackPlaysFirst) {
10578         currentMove = forwardMostMove = backwardMostMove = 1;
10579         strcpy(moveList[0], "");
10580         strcpy(parseList[0], "");
10581         CopyBoard(boards[1], initial_position);
10582         DisplayMessage("", _("Black to play"));
10583     } else {
10584         currentMove = forwardMostMove = backwardMostMove = 0;
10585         DisplayMessage("", _("White to play"));
10586     }
10587     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10588     SendBoard(&first, forwardMostMove);
10589     if (appData.debugMode) {
10590 int i, j;
10591   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10592   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10593         fprintf(debugFP, "Load Position\n");
10594     }
10595
10596     if (positionNumber > 1) {
10597         sprintf(line, "%s %d", title, positionNumber);
10598         DisplayTitle(line);
10599     } else {
10600         DisplayTitle(title);
10601     }
10602     gameMode = EditGame;
10603     ModeHighlight();
10604     ResetClocks();
10605     timeRemaining[0][1] = whiteTimeRemaining;
10606     timeRemaining[1][1] = blackTimeRemaining;
10607     DrawPosition(FALSE, boards[currentMove]);
10608    
10609     return TRUE;
10610 }
10611
10612
10613 void
10614 CopyPlayerNameIntoFileName(dest, src)
10615      char **dest, *src;
10616 {
10617     while (*src != NULLCHAR && *src != ',') {
10618         if (*src == ' ') {
10619             *(*dest)++ = '_';
10620             src++;
10621         } else {
10622             *(*dest)++ = *src++;
10623         }
10624     }
10625 }
10626
10627 char *DefaultFileName(ext)
10628      char *ext;
10629 {
10630     static char def[MSG_SIZ];
10631     char *p;
10632
10633     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10634         p = def;
10635         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10636         *p++ = '-';
10637         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10638         *p++ = '.';
10639         strcpy(p, ext);
10640     } else {
10641         def[0] = NULLCHAR;
10642     }
10643     return def;
10644 }
10645
10646 /* Save the current game to the given file */
10647 int
10648 SaveGameToFile(filename, append)
10649      char *filename;
10650      int append;
10651 {
10652     FILE *f;
10653     char buf[MSG_SIZ];
10654
10655     if (strcmp(filename, "-") == 0) {
10656         return SaveGame(stdout, 0, NULL);
10657     } else {
10658         f = fopen(filename, append ? "a" : "w");
10659         if (f == NULL) {
10660             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10661             DisplayError(buf, errno);
10662             return FALSE;
10663         } else {
10664             return SaveGame(f, 0, NULL);
10665         }
10666     }
10667 }
10668
10669 char *
10670 SavePart(str)
10671      char *str;
10672 {
10673     static char buf[MSG_SIZ];
10674     char *p;
10675     
10676     p = strchr(str, ' ');
10677     if (p == NULL) return str;
10678     strncpy(buf, str, p - str);
10679     buf[p - str] = NULLCHAR;
10680     return buf;
10681 }
10682
10683 #define PGN_MAX_LINE 75
10684
10685 #define PGN_SIDE_WHITE  0
10686 #define PGN_SIDE_BLACK  1
10687
10688 /* [AS] */
10689 static int FindFirstMoveOutOfBook( int side )
10690 {
10691     int result = -1;
10692
10693     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10694         int index = backwardMostMove;
10695         int has_book_hit = 0;
10696
10697         if( (index % 2) != side ) {
10698             index++;
10699         }
10700
10701         while( index < forwardMostMove ) {
10702             /* Check to see if engine is in book */
10703             int depth = pvInfoList[index].depth;
10704             int score = pvInfoList[index].score;
10705             int in_book = 0;
10706
10707             if( depth <= 2 ) {
10708                 in_book = 1;
10709             }
10710             else if( score == 0 && depth == 63 ) {
10711                 in_book = 1; /* Zappa */
10712             }
10713             else if( score == 2 && depth == 99 ) {
10714                 in_book = 1; /* Abrok */
10715             }
10716
10717             has_book_hit += in_book;
10718
10719             if( ! in_book ) {
10720                 result = index;
10721
10722                 break;
10723             }
10724
10725             index += 2;
10726         }
10727     }
10728
10729     return result;
10730 }
10731
10732 /* [AS] */
10733 void GetOutOfBookInfo( char * buf )
10734 {
10735     int oob[2];
10736     int i;
10737     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10738
10739     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10740     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10741
10742     *buf = '\0';
10743
10744     if( oob[0] >= 0 || oob[1] >= 0 ) {
10745         for( i=0; i<2; i++ ) {
10746             int idx = oob[i];
10747
10748             if( idx >= 0 ) {
10749                 if( i > 0 && oob[0] >= 0 ) {
10750                     strcat( buf, "   " );
10751                 }
10752
10753                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10754                 sprintf( buf+strlen(buf), "%s%.2f", 
10755                     pvInfoList[idx].score >= 0 ? "+" : "",
10756                     pvInfoList[idx].score / 100.0 );
10757             }
10758         }
10759     }
10760 }
10761
10762 /* Save game in PGN style and close the file */
10763 int
10764 SaveGamePGN(f)
10765      FILE *f;
10766 {
10767     int i, offset, linelen, newblock;
10768     time_t tm;
10769 //    char *movetext;
10770     char numtext[32];
10771     int movelen, numlen, blank;
10772     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10773
10774     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10775     
10776     tm = time((time_t *) NULL);
10777     
10778     PrintPGNTags(f, &gameInfo);
10779     
10780     if (backwardMostMove > 0 || startedFromSetupPosition) {
10781         char *fen = PositionToFEN(backwardMostMove, NULL);
10782         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10783         fprintf(f, "\n{--------------\n");
10784         PrintPosition(f, backwardMostMove);
10785         fprintf(f, "--------------}\n");
10786         free(fen);
10787     }
10788     else {
10789         /* [AS] Out of book annotation */
10790         if( appData.saveOutOfBookInfo ) {
10791             char buf[64];
10792
10793             GetOutOfBookInfo( buf );
10794
10795             if( buf[0] != '\0' ) {
10796                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10797             }
10798         }
10799
10800         fprintf(f, "\n");
10801     }
10802
10803     i = backwardMostMove;
10804     linelen = 0;
10805     newblock = TRUE;
10806
10807     while (i < forwardMostMove) {
10808         /* Print comments preceding this move */
10809         if (commentList[i] != NULL) {
10810             if (linelen > 0) fprintf(f, "\n");
10811             fprintf(f, "%s", commentList[i]);
10812             linelen = 0;
10813             newblock = TRUE;
10814         }
10815
10816         /* Format move number */
10817         if ((i % 2) == 0) {
10818             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10819         } else {
10820             if (newblock) {
10821                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10822             } else {
10823                 numtext[0] = NULLCHAR;
10824             }
10825         }
10826         numlen = strlen(numtext);
10827         newblock = FALSE;
10828
10829         /* Print move number */
10830         blank = linelen > 0 && numlen > 0;
10831         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10832             fprintf(f, "\n");
10833             linelen = 0;
10834             blank = 0;
10835         }
10836         if (blank) {
10837             fprintf(f, " ");
10838             linelen++;
10839         }
10840         fprintf(f, "%s", numtext);
10841         linelen += numlen;
10842
10843         /* Get move */
10844         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10845         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10846
10847         /* Print move */
10848         blank = linelen > 0 && movelen > 0;
10849         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10850             fprintf(f, "\n");
10851             linelen = 0;
10852             blank = 0;
10853         }
10854         if (blank) {
10855             fprintf(f, " ");
10856             linelen++;
10857         }
10858         fprintf(f, "%s", move_buffer);
10859         linelen += movelen;
10860
10861         /* [AS] Add PV info if present */
10862         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10863             /* [HGM] add time */
10864             char buf[MSG_SIZ]; int seconds;
10865
10866             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10867
10868             if( seconds <= 0) buf[0] = 0; else
10869             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10870                 seconds = (seconds + 4)/10; // round to full seconds
10871                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10872                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10873             }
10874
10875             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10876                 pvInfoList[i].score >= 0 ? "+" : "",
10877                 pvInfoList[i].score / 100.0,
10878                 pvInfoList[i].depth,
10879                 buf );
10880
10881             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10882
10883             /* Print score/depth */
10884             blank = linelen > 0 && movelen > 0;
10885             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10886                 fprintf(f, "\n");
10887                 linelen = 0;
10888                 blank = 0;
10889             }
10890             if (blank) {
10891                 fprintf(f, " ");
10892                 linelen++;
10893             }
10894             fprintf(f, "%s", move_buffer);
10895             linelen += movelen;
10896         }
10897
10898         i++;
10899     }
10900     
10901     /* Start a new line */
10902     if (linelen > 0) fprintf(f, "\n");
10903
10904     /* Print comments after last move */
10905     if (commentList[i] != NULL) {
10906         fprintf(f, "%s\n", commentList[i]);
10907     }
10908
10909     /* Print result */
10910     if (gameInfo.resultDetails != NULL &&
10911         gameInfo.resultDetails[0] != NULLCHAR) {
10912         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10913                 PGNResult(gameInfo.result));
10914     } else {
10915         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10916     }
10917
10918     fclose(f);
10919     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10920     return TRUE;
10921 }
10922
10923 /* Save game in old style and close the file */
10924 int
10925 SaveGameOldStyle(f)
10926      FILE *f;
10927 {
10928     int i, offset;
10929     time_t tm;
10930     
10931     tm = time((time_t *) NULL);
10932     
10933     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10934     PrintOpponents(f);
10935     
10936     if (backwardMostMove > 0 || startedFromSetupPosition) {
10937         fprintf(f, "\n[--------------\n");
10938         PrintPosition(f, backwardMostMove);
10939         fprintf(f, "--------------]\n");
10940     } else {
10941         fprintf(f, "\n");
10942     }
10943
10944     i = backwardMostMove;
10945     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10946
10947     while (i < forwardMostMove) {
10948         if (commentList[i] != NULL) {
10949             fprintf(f, "[%s]\n", commentList[i]);
10950         }
10951
10952         if ((i % 2) == 1) {
10953             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10954             i++;
10955         } else {
10956             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10957             i++;
10958             if (commentList[i] != NULL) {
10959                 fprintf(f, "\n");
10960                 continue;
10961             }
10962             if (i >= forwardMostMove) {
10963                 fprintf(f, "\n");
10964                 break;
10965             }
10966             fprintf(f, "%s\n", parseList[i]);
10967             i++;
10968         }
10969     }
10970     
10971     if (commentList[i] != NULL) {
10972         fprintf(f, "[%s]\n", commentList[i]);
10973     }
10974
10975     /* This isn't really the old style, but it's close enough */
10976     if (gameInfo.resultDetails != NULL &&
10977         gameInfo.resultDetails[0] != NULLCHAR) {
10978         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10979                 gameInfo.resultDetails);
10980     } else {
10981         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10982     }
10983
10984     fclose(f);
10985     return TRUE;
10986 }
10987
10988 /* Save the current game to open file f and close the file */
10989 int
10990 SaveGame(f, dummy, dummy2)
10991      FILE *f;
10992      int dummy;
10993      char *dummy2;
10994 {
10995     if (gameMode == EditPosition) EditPositionDone(TRUE);
10996     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10997     if (appData.oldSaveStyle)
10998       return SaveGameOldStyle(f);
10999     else
11000       return SaveGamePGN(f);
11001 }
11002
11003 /* Save the current position to the given file */
11004 int
11005 SavePositionToFile(filename)
11006      char *filename;
11007 {
11008     FILE *f;
11009     char buf[MSG_SIZ];
11010
11011     if (strcmp(filename, "-") == 0) {
11012         return SavePosition(stdout, 0, NULL);
11013     } else {
11014         f = fopen(filename, "a");
11015         if (f == NULL) {
11016             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11017             DisplayError(buf, errno);
11018             return FALSE;
11019         } else {
11020             SavePosition(f, 0, NULL);
11021             return TRUE;
11022         }
11023     }
11024 }
11025
11026 /* Save the current position to the given open file and close the file */
11027 int
11028 SavePosition(f, dummy, dummy2)
11029      FILE *f;
11030      int dummy;
11031      char *dummy2;
11032 {
11033     time_t tm;
11034     char *fen;
11035     
11036     if (gameMode == EditPosition) EditPositionDone(TRUE);
11037     if (appData.oldSaveStyle) {
11038         tm = time((time_t *) NULL);
11039     
11040         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11041         PrintOpponents(f);
11042         fprintf(f, "[--------------\n");
11043         PrintPosition(f, currentMove);
11044         fprintf(f, "--------------]\n");
11045     } else {
11046         fen = PositionToFEN(currentMove, NULL);
11047         fprintf(f, "%s\n", fen);
11048         free(fen);
11049     }
11050     fclose(f);
11051     return TRUE;
11052 }
11053
11054 void
11055 ReloadCmailMsgEvent(unregister)
11056      int unregister;
11057 {
11058 #if !WIN32
11059     static char *inFilename = NULL;
11060     static char *outFilename;
11061     int i;
11062     struct stat inbuf, outbuf;
11063     int status;
11064     
11065     /* Any registered moves are unregistered if unregister is set, */
11066     /* i.e. invoked by the signal handler */
11067     if (unregister) {
11068         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11069             cmailMoveRegistered[i] = FALSE;
11070             if (cmailCommentList[i] != NULL) {
11071                 free(cmailCommentList[i]);
11072                 cmailCommentList[i] = NULL;
11073             }
11074         }
11075         nCmailMovesRegistered = 0;
11076     }
11077
11078     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11079         cmailResult[i] = CMAIL_NOT_RESULT;
11080     }
11081     nCmailResults = 0;
11082
11083     if (inFilename == NULL) {
11084         /* Because the filenames are static they only get malloced once  */
11085         /* and they never get freed                                      */
11086         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11087         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11088
11089         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11090         sprintf(outFilename, "%s.out", appData.cmailGameName);
11091     }
11092     
11093     status = stat(outFilename, &outbuf);
11094     if (status < 0) {
11095         cmailMailedMove = FALSE;
11096     } else {
11097         status = stat(inFilename, &inbuf);
11098         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11099     }
11100     
11101     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11102        counts the games, notes how each one terminated, etc.
11103        
11104        It would be nice to remove this kludge and instead gather all
11105        the information while building the game list.  (And to keep it
11106        in the game list nodes instead of having a bunch of fixed-size
11107        parallel arrays.)  Note this will require getting each game's
11108        termination from the PGN tags, as the game list builder does
11109        not process the game moves.  --mann
11110        */
11111     cmailMsgLoaded = TRUE;
11112     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11113     
11114     /* Load first game in the file or popup game menu */
11115     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11116
11117 #endif /* !WIN32 */
11118     return;
11119 }
11120
11121 int
11122 RegisterMove()
11123 {
11124     FILE *f;
11125     char string[MSG_SIZ];
11126
11127     if (   cmailMailedMove
11128         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11129         return TRUE;            /* Allow free viewing  */
11130     }
11131
11132     /* Unregister move to ensure that we don't leave RegisterMove        */
11133     /* with the move registered when the conditions for registering no   */
11134     /* longer hold                                                       */
11135     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11136         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11137         nCmailMovesRegistered --;
11138
11139         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
11140           {
11141               free(cmailCommentList[lastLoadGameNumber - 1]);
11142               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11143           }
11144     }
11145
11146     if (cmailOldMove == -1) {
11147         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11148         return FALSE;
11149     }
11150
11151     if (currentMove > cmailOldMove + 1) {
11152         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11153         return FALSE;
11154     }
11155
11156     if (currentMove < cmailOldMove) {
11157         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11158         return FALSE;
11159     }
11160
11161     if (forwardMostMove > currentMove) {
11162         /* Silently truncate extra moves */
11163         TruncateGame();
11164     }
11165
11166     if (   (currentMove == cmailOldMove + 1)
11167         || (   (currentMove == cmailOldMove)
11168             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11169                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11170         if (gameInfo.result != GameUnfinished) {
11171             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11172         }
11173
11174         if (commentList[currentMove] != NULL) {
11175             cmailCommentList[lastLoadGameNumber - 1]
11176               = StrSave(commentList[currentMove]);
11177         }
11178         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11179
11180         if (appData.debugMode)
11181           fprintf(debugFP, "Saving %s for game %d\n",
11182                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11183
11184         sprintf(string,
11185                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11186         
11187         f = fopen(string, "w");
11188         if (appData.oldSaveStyle) {
11189             SaveGameOldStyle(f); /* also closes the file */
11190             
11191             sprintf(string, "%s.pos.out", appData.cmailGameName);
11192             f = fopen(string, "w");
11193             SavePosition(f, 0, NULL); /* also closes the file */
11194         } else {
11195             fprintf(f, "{--------------\n");
11196             PrintPosition(f, currentMove);
11197             fprintf(f, "--------------}\n\n");
11198             
11199             SaveGame(f, 0, NULL); /* also closes the file*/
11200         }
11201         
11202         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11203         nCmailMovesRegistered ++;
11204     } else if (nCmailGames == 1) {
11205         DisplayError(_("You have not made a move yet"), 0);
11206         return FALSE;
11207     }
11208
11209     return TRUE;
11210 }
11211
11212 void
11213 MailMoveEvent()
11214 {
11215 #if !WIN32
11216     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11217     FILE *commandOutput;
11218     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11219     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11220     int nBuffers;
11221     int i;
11222     int archived;
11223     char *arcDir;
11224
11225     if (! cmailMsgLoaded) {
11226         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11227         return;
11228     }
11229
11230     if (nCmailGames == nCmailResults) {
11231         DisplayError(_("No unfinished games"), 0);
11232         return;
11233     }
11234
11235 #if CMAIL_PROHIBIT_REMAIL
11236     if (cmailMailedMove) {
11237         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);
11238         DisplayError(msg, 0);
11239         return;
11240     }
11241 #endif
11242
11243     if (! (cmailMailedMove || RegisterMove())) return;
11244     
11245     if (   cmailMailedMove
11246         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11247         sprintf(string, partCommandString,
11248                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11249         commandOutput = popen(string, "r");
11250
11251         if (commandOutput == NULL) {
11252             DisplayError(_("Failed to invoke cmail"), 0);
11253         } else {
11254             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11255                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11256             }
11257             if (nBuffers > 1) {
11258                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11259                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11260                 nBytes = MSG_SIZ - 1;
11261             } else {
11262                 (void) memcpy(msg, buffer, nBytes);
11263             }
11264             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11265
11266             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11267                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11268
11269                 archived = TRUE;
11270                 for (i = 0; i < nCmailGames; i ++) {
11271                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11272                         archived = FALSE;
11273                     }
11274                 }
11275                 if (   archived
11276                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11277                         != NULL)) {
11278                     sprintf(buffer, "%s/%s.%s.archive",
11279                             arcDir,
11280                             appData.cmailGameName,
11281                             gameInfo.date);
11282                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11283                     cmailMsgLoaded = FALSE;
11284                 }
11285             }
11286
11287             DisplayInformation(msg);
11288             pclose(commandOutput);
11289         }
11290     } else {
11291         if ((*cmailMsg) != '\0') {
11292             DisplayInformation(cmailMsg);
11293         }
11294     }
11295
11296     return;
11297 #endif /* !WIN32 */
11298 }
11299
11300 char *
11301 CmailMsg()
11302 {
11303 #if WIN32
11304     return NULL;
11305 #else
11306     int  prependComma = 0;
11307     char number[5];
11308     char string[MSG_SIZ];       /* Space for game-list */
11309     int  i;
11310     
11311     if (!cmailMsgLoaded) return "";
11312
11313     if (cmailMailedMove) {
11314         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11315     } else {
11316         /* Create a list of games left */
11317         sprintf(string, "[");
11318         for (i = 0; i < nCmailGames; i ++) {
11319             if (! (   cmailMoveRegistered[i]
11320                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11321                 if (prependComma) {
11322                     sprintf(number, ",%d", i + 1);
11323                 } else {
11324                     sprintf(number, "%d", i + 1);
11325                     prependComma = 1;
11326                 }
11327                 
11328                 strcat(string, number);
11329             }
11330         }
11331         strcat(string, "]");
11332
11333         if (nCmailMovesRegistered + nCmailResults == 0) {
11334             switch (nCmailGames) {
11335               case 1:
11336                 sprintf(cmailMsg,
11337                         _("Still need to make move for game\n"));
11338                 break;
11339                 
11340               case 2:
11341                 sprintf(cmailMsg,
11342                         _("Still need to make moves for both games\n"));
11343                 break;
11344                 
11345               default:
11346                 sprintf(cmailMsg,
11347                         _("Still need to make moves for all %d games\n"),
11348                         nCmailGames);
11349                 break;
11350             }
11351         } else {
11352             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11353               case 1:
11354                 sprintf(cmailMsg,
11355                         _("Still need to make a move for game %s\n"),
11356                         string);
11357                 break;
11358                 
11359               case 0:
11360                 if (nCmailResults == nCmailGames) {
11361                     sprintf(cmailMsg, _("No unfinished games\n"));
11362                 } else {
11363                     sprintf(cmailMsg, _("Ready to send mail\n"));
11364                 }
11365                 break;
11366                 
11367               default:
11368                 sprintf(cmailMsg,
11369                         _("Still need to make moves for games %s\n"),
11370                         string);
11371             }
11372         }
11373     }
11374     return cmailMsg;
11375 #endif /* WIN32 */
11376 }
11377
11378 void
11379 ResetGameEvent()
11380 {
11381     if (gameMode == Training)
11382       SetTrainingModeOff();
11383
11384     Reset(TRUE, TRUE);
11385     cmailMsgLoaded = FALSE;
11386     if (appData.icsActive) {
11387       SendToICS(ics_prefix);
11388       SendToICS("refresh\n");
11389     }
11390 }
11391
11392 void
11393 ExitEvent(status)
11394      int status;
11395 {
11396     exiting++;
11397     if (exiting > 2) {
11398       /* Give up on clean exit */
11399       exit(status);
11400     }
11401     if (exiting > 1) {
11402       /* Keep trying for clean exit */
11403       return;
11404     }
11405
11406     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11407
11408     if (telnetISR != NULL) {
11409       RemoveInputSource(telnetISR);
11410     }
11411     if (icsPR != NoProc) {
11412       DestroyChildProcess(icsPR, TRUE);
11413     }
11414
11415     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11416     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11417
11418     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11419     /* make sure this other one finishes before killing it!                  */
11420     if(endingGame) { int count = 0;
11421         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11422         while(endingGame && count++ < 10) DoSleep(1);
11423         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11424     }
11425
11426     /* Kill off chess programs */
11427     if (first.pr != NoProc) {
11428         ExitAnalyzeMode();
11429         
11430         DoSleep( appData.delayBeforeQuit );
11431         SendToProgram("quit\n", &first);
11432         DoSleep( appData.delayAfterQuit );
11433         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11434     }
11435     if (second.pr != NoProc) {
11436         DoSleep( appData.delayBeforeQuit );
11437         SendToProgram("quit\n", &second);
11438         DoSleep( appData.delayAfterQuit );
11439         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11440     }
11441     if (first.isr != NULL) {
11442         RemoveInputSource(first.isr);
11443     }
11444     if (second.isr != NULL) {
11445         RemoveInputSource(second.isr);
11446     }
11447
11448     ShutDownFrontEnd();
11449     exit(status);
11450 }
11451
11452 void
11453 PauseEvent()
11454 {
11455     if (appData.debugMode)
11456         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11457     if (pausing) {
11458         pausing = FALSE;
11459         ModeHighlight();
11460         if (gameMode == MachinePlaysWhite ||
11461             gameMode == MachinePlaysBlack) {
11462             StartClocks();
11463         } else {
11464             DisplayBothClocks();
11465         }
11466         if (gameMode == PlayFromGameFile) {
11467             if (appData.timeDelay >= 0) 
11468                 AutoPlayGameLoop();
11469         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11470             Reset(FALSE, TRUE);
11471             SendToICS(ics_prefix);
11472             SendToICS("refresh\n");
11473         } else if (currentMove < forwardMostMove) {
11474             ForwardInner(forwardMostMove);
11475         }
11476         pauseExamInvalid = FALSE;
11477     } else {
11478         switch (gameMode) {
11479           default:
11480             return;
11481           case IcsExamining:
11482             pauseExamForwardMostMove = forwardMostMove;
11483             pauseExamInvalid = FALSE;
11484             /* fall through */
11485           case IcsObserving:
11486           case IcsPlayingWhite:
11487           case IcsPlayingBlack:
11488             pausing = TRUE;
11489             ModeHighlight();
11490             return;
11491           case PlayFromGameFile:
11492             (void) StopLoadGameTimer();
11493             pausing = TRUE;
11494             ModeHighlight();
11495             break;
11496           case BeginningOfGame:
11497             if (appData.icsActive) return;
11498             /* else fall through */
11499           case MachinePlaysWhite:
11500           case MachinePlaysBlack:
11501           case TwoMachinesPlay:
11502             if (forwardMostMove == 0)
11503               return;           /* don't pause if no one has moved */
11504             if ((gameMode == MachinePlaysWhite &&
11505                  !WhiteOnMove(forwardMostMove)) ||
11506                 (gameMode == MachinePlaysBlack &&
11507                  WhiteOnMove(forwardMostMove))) {
11508                 StopClocks();
11509             }
11510             pausing = TRUE;
11511             ModeHighlight();
11512             break;
11513         }
11514     }
11515 }
11516
11517 void
11518 EditCommentEvent()
11519 {
11520     char title[MSG_SIZ];
11521
11522     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11523         strcpy(title, _("Edit comment"));
11524     } else {
11525         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11526                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11527                 parseList[currentMove - 1]);
11528     }
11529
11530     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11531 }
11532
11533
11534 void
11535 EditTagsEvent()
11536 {
11537     char *tags = PGNTags(&gameInfo);
11538     EditTagsPopUp(tags);
11539     free(tags);
11540 }
11541
11542 void
11543 AnalyzeModeEvent()
11544 {
11545     if (appData.noChessProgram || gameMode == AnalyzeMode)
11546       return;
11547
11548     if (gameMode != AnalyzeFile) {
11549         if (!appData.icsEngineAnalyze) {
11550                EditGameEvent();
11551                if (gameMode != EditGame) return;
11552         }
11553         ResurrectChessProgram();
11554         SendToProgram("analyze\n", &first);
11555         first.analyzing = TRUE;
11556         /*first.maybeThinking = TRUE;*/
11557         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11558         EngineOutputPopUp();
11559     }
11560     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11561     pausing = FALSE;
11562     ModeHighlight();
11563     SetGameInfo();
11564
11565     StartAnalysisClock();
11566     GetTimeMark(&lastNodeCountTime);
11567     lastNodeCount = 0;
11568 }
11569
11570 void
11571 AnalyzeFileEvent()
11572 {
11573     if (appData.noChessProgram || gameMode == AnalyzeFile)
11574       return;
11575
11576     if (gameMode != AnalyzeMode) {
11577         EditGameEvent();
11578         if (gameMode != EditGame) return;
11579         ResurrectChessProgram();
11580         SendToProgram("analyze\n", &first);
11581         first.analyzing = TRUE;
11582         /*first.maybeThinking = TRUE;*/
11583         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11584         EngineOutputPopUp();
11585     }
11586     gameMode = AnalyzeFile;
11587     pausing = FALSE;
11588     ModeHighlight();
11589     SetGameInfo();
11590
11591     StartAnalysisClock();
11592     GetTimeMark(&lastNodeCountTime);
11593     lastNodeCount = 0;
11594 }
11595
11596 void
11597 MachineWhiteEvent()
11598 {
11599     char buf[MSG_SIZ];
11600     char *bookHit = NULL;
11601
11602     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11603       return;
11604
11605
11606     if (gameMode == PlayFromGameFile || 
11607         gameMode == TwoMachinesPlay  || 
11608         gameMode == Training         || 
11609         gameMode == AnalyzeMode      || 
11610         gameMode == EndOfGame)
11611         EditGameEvent();
11612
11613     if (gameMode == EditPosition) 
11614         EditPositionDone(TRUE);
11615
11616     if (!WhiteOnMove(currentMove)) {
11617         DisplayError(_("It is not White's turn"), 0);
11618         return;
11619     }
11620   
11621     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11622       ExitAnalyzeMode();
11623
11624     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11625         gameMode == AnalyzeFile)
11626         TruncateGame();
11627
11628     ResurrectChessProgram();    /* in case it isn't running */
11629     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11630         gameMode = MachinePlaysWhite;
11631         ResetClocks();
11632     } else
11633     gameMode = MachinePlaysWhite;
11634     pausing = FALSE;
11635     ModeHighlight();
11636     SetGameInfo();
11637     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11638     DisplayTitle(buf);
11639     if (first.sendName) {
11640       sprintf(buf, "name %s\n", gameInfo.black);
11641       SendToProgram(buf, &first);
11642     }
11643     if (first.sendTime) {
11644       if (first.useColors) {
11645         SendToProgram("black\n", &first); /*gnu kludge*/
11646       }
11647       SendTimeRemaining(&first, TRUE);
11648     }
11649     if (first.useColors) {
11650       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11651     }
11652     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11653     SetMachineThinkingEnables();
11654     first.maybeThinking = TRUE;
11655     StartClocks();
11656     firstMove = FALSE;
11657
11658     if (appData.autoFlipView && !flipView) {
11659       flipView = !flipView;
11660       DrawPosition(FALSE, NULL);
11661       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11662     }
11663
11664     if(bookHit) { // [HGM] book: simulate book reply
11665         static char bookMove[MSG_SIZ]; // a bit generous?
11666
11667         programStats.nodes = programStats.depth = programStats.time = 
11668         programStats.score = programStats.got_only_move = 0;
11669         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11670
11671         strcpy(bookMove, "move ");
11672         strcat(bookMove, bookHit);
11673         HandleMachineMove(bookMove, &first);
11674     }
11675 }
11676
11677 void
11678 MachineBlackEvent()
11679 {
11680     char buf[MSG_SIZ];
11681    char *bookHit = NULL;
11682
11683     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11684         return;
11685
11686
11687     if (gameMode == PlayFromGameFile || 
11688         gameMode == TwoMachinesPlay  || 
11689         gameMode == Training         || 
11690         gameMode == AnalyzeMode      || 
11691         gameMode == EndOfGame)
11692         EditGameEvent();
11693
11694     if (gameMode == EditPosition) 
11695         EditPositionDone(TRUE);
11696
11697     if (WhiteOnMove(currentMove)) {
11698         DisplayError(_("It is not Black's turn"), 0);
11699         return;
11700     }
11701     
11702     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11703       ExitAnalyzeMode();
11704
11705     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11706         gameMode == AnalyzeFile)
11707         TruncateGame();
11708
11709     ResurrectChessProgram();    /* in case it isn't running */
11710     gameMode = MachinePlaysBlack;
11711     pausing = FALSE;
11712     ModeHighlight();
11713     SetGameInfo();
11714     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11715     DisplayTitle(buf);
11716     if (first.sendName) {
11717       sprintf(buf, "name %s\n", gameInfo.white);
11718       SendToProgram(buf, &first);
11719     }
11720     if (first.sendTime) {
11721       if (first.useColors) {
11722         SendToProgram("white\n", &first); /*gnu kludge*/
11723       }
11724       SendTimeRemaining(&first, FALSE);
11725     }
11726     if (first.useColors) {
11727       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11728     }
11729     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11730     SetMachineThinkingEnables();
11731     first.maybeThinking = TRUE;
11732     StartClocks();
11733
11734     if (appData.autoFlipView && flipView) {
11735       flipView = !flipView;
11736       DrawPosition(FALSE, NULL);
11737       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11738     }
11739     if(bookHit) { // [HGM] book: simulate book reply
11740         static char bookMove[MSG_SIZ]; // a bit generous?
11741
11742         programStats.nodes = programStats.depth = programStats.time = 
11743         programStats.score = programStats.got_only_move = 0;
11744         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11745
11746         strcpy(bookMove, "move ");
11747         strcat(bookMove, bookHit);
11748         HandleMachineMove(bookMove, &first);
11749     }
11750 }
11751
11752
11753 void
11754 DisplayTwoMachinesTitle()
11755 {
11756     char buf[MSG_SIZ];
11757     if (appData.matchGames > 0) {
11758         if (first.twoMachinesColor[0] == 'w') {
11759             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11760                     gameInfo.white, gameInfo.black,
11761                     first.matchWins, second.matchWins,
11762                     matchGame - 1 - (first.matchWins + second.matchWins));
11763         } else {
11764             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11765                     gameInfo.white, gameInfo.black,
11766                     second.matchWins, first.matchWins,
11767                     matchGame - 1 - (first.matchWins + second.matchWins));
11768         }
11769     } else {
11770         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11771     }
11772     DisplayTitle(buf);
11773 }
11774
11775 void
11776 TwoMachinesEvent P((void))
11777 {
11778     int i;
11779     char buf[MSG_SIZ];
11780     ChessProgramState *onmove;
11781     char *bookHit = NULL;
11782     
11783     if (appData.noChessProgram) return;
11784
11785     switch (gameMode) {
11786       case TwoMachinesPlay:
11787         return;
11788       case MachinePlaysWhite:
11789       case MachinePlaysBlack:
11790         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11791             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11792             return;
11793         }
11794         /* fall through */
11795       case BeginningOfGame:
11796       case PlayFromGameFile:
11797       case EndOfGame:
11798         EditGameEvent();
11799         if (gameMode != EditGame) return;
11800         break;
11801       case EditPosition:
11802         EditPositionDone(TRUE);
11803         break;
11804       case AnalyzeMode:
11805       case AnalyzeFile:
11806         ExitAnalyzeMode();
11807         break;
11808       case EditGame:
11809       default:
11810         break;
11811     }
11812
11813 //    forwardMostMove = currentMove;
11814     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11815     ResurrectChessProgram();    /* in case first program isn't running */
11816
11817     if (second.pr == NULL) {
11818         StartChessProgram(&second);
11819         if (second.protocolVersion == 1) {
11820           TwoMachinesEventIfReady();
11821         } else {
11822           /* kludge: allow timeout for initial "feature" command */
11823           FreezeUI();
11824           DisplayMessage("", _("Starting second chess program"));
11825           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11826         }
11827         return;
11828     }
11829     DisplayMessage("", "");
11830     InitChessProgram(&second, FALSE);
11831     SendToProgram("force\n", &second);
11832     if (startedFromSetupPosition) {
11833         SendBoard(&second, backwardMostMove);
11834     if (appData.debugMode) {
11835         fprintf(debugFP, "Two Machines\n");
11836     }
11837     }
11838     for (i = backwardMostMove; i < forwardMostMove; i++) {
11839         SendMoveToProgram(i, &second);
11840     }
11841
11842     gameMode = TwoMachinesPlay;
11843     pausing = FALSE;
11844     ModeHighlight();
11845     SetGameInfo();
11846     DisplayTwoMachinesTitle();
11847     firstMove = TRUE;
11848     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11849         onmove = &first;
11850     } else {
11851         onmove = &second;
11852     }
11853
11854     SendToProgram(first.computerString, &first);
11855     if (first.sendName) {
11856       sprintf(buf, "name %s\n", second.tidy);
11857       SendToProgram(buf, &first);
11858     }
11859     SendToProgram(second.computerString, &second);
11860     if (second.sendName) {
11861       sprintf(buf, "name %s\n", first.tidy);
11862       SendToProgram(buf, &second);
11863     }
11864
11865     ResetClocks();
11866     if (!first.sendTime || !second.sendTime) {
11867         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11868         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11869     }
11870     if (onmove->sendTime) {
11871       if (onmove->useColors) {
11872         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11873       }
11874       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11875     }
11876     if (onmove->useColors) {
11877       SendToProgram(onmove->twoMachinesColor, onmove);
11878     }
11879     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11880 //    SendToProgram("go\n", onmove);
11881     onmove->maybeThinking = TRUE;
11882     SetMachineThinkingEnables();
11883
11884     StartClocks();
11885
11886     if(bookHit) { // [HGM] book: simulate book reply
11887         static char bookMove[MSG_SIZ]; // a bit generous?
11888
11889         programStats.nodes = programStats.depth = programStats.time = 
11890         programStats.score = programStats.got_only_move = 0;
11891         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11892
11893         strcpy(bookMove, "move ");
11894         strcat(bookMove, bookHit);
11895         savedMessage = bookMove; // args for deferred call
11896         savedState = onmove;
11897         ScheduleDelayedEvent(DeferredBookMove, 1);
11898     }
11899 }
11900
11901 void
11902 TrainingEvent()
11903 {
11904     if (gameMode == Training) {
11905       SetTrainingModeOff();
11906       gameMode = PlayFromGameFile;
11907       DisplayMessage("", _("Training mode off"));
11908     } else {
11909       gameMode = Training;
11910       animateTraining = appData.animate;
11911
11912       /* make sure we are not already at the end of the game */
11913       if (currentMove < forwardMostMove) {
11914         SetTrainingModeOn();
11915         DisplayMessage("", _("Training mode on"));
11916       } else {
11917         gameMode = PlayFromGameFile;
11918         DisplayError(_("Already at end of game"), 0);
11919       }
11920     }
11921     ModeHighlight();
11922 }
11923
11924 void
11925 IcsClientEvent()
11926 {
11927     if (!appData.icsActive) return;
11928     switch (gameMode) {
11929       case IcsPlayingWhite:
11930       case IcsPlayingBlack:
11931       case IcsObserving:
11932       case IcsIdle:
11933       case BeginningOfGame:
11934       case IcsExamining:
11935         return;
11936
11937       case EditGame:
11938         break;
11939
11940       case EditPosition:
11941         EditPositionDone(TRUE);
11942         break;
11943
11944       case AnalyzeMode:
11945       case AnalyzeFile:
11946         ExitAnalyzeMode();
11947         break;
11948         
11949       default:
11950         EditGameEvent();
11951         break;
11952     }
11953
11954     gameMode = IcsIdle;
11955     ModeHighlight();
11956     return;
11957 }
11958
11959
11960 void
11961 EditGameEvent()
11962 {
11963     int i;
11964
11965     switch (gameMode) {
11966       case Training:
11967         SetTrainingModeOff();
11968         break;
11969       case MachinePlaysWhite:
11970       case MachinePlaysBlack:
11971       case BeginningOfGame:
11972         SendToProgram("force\n", &first);
11973         SetUserThinkingEnables();
11974         break;
11975       case PlayFromGameFile:
11976         (void) StopLoadGameTimer();
11977         if (gameFileFP != NULL) {
11978             gameFileFP = NULL;
11979         }
11980         break;
11981       case EditPosition:
11982         EditPositionDone(TRUE);
11983         break;
11984       case AnalyzeMode:
11985       case AnalyzeFile:
11986         ExitAnalyzeMode();
11987         SendToProgram("force\n", &first);
11988         break;
11989       case TwoMachinesPlay:
11990         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11991         ResurrectChessProgram();
11992         SetUserThinkingEnables();
11993         break;
11994       case EndOfGame:
11995         ResurrectChessProgram();
11996         break;
11997       case IcsPlayingBlack:
11998       case IcsPlayingWhite:
11999         DisplayError(_("Warning: You are still playing a game"), 0);
12000         break;
12001       case IcsObserving:
12002         DisplayError(_("Warning: You are still observing a game"), 0);
12003         break;
12004       case IcsExamining:
12005         DisplayError(_("Warning: You are still examining a game"), 0);
12006         break;
12007       case IcsIdle:
12008         break;
12009       case EditGame:
12010       default:
12011         return;
12012     }
12013     
12014     pausing = FALSE;
12015     StopClocks();
12016     first.offeredDraw = second.offeredDraw = 0;
12017
12018     if (gameMode == PlayFromGameFile) {
12019         whiteTimeRemaining = timeRemaining[0][currentMove];
12020         blackTimeRemaining = timeRemaining[1][currentMove];
12021         DisplayTitle("");
12022     }
12023
12024     if (gameMode == MachinePlaysWhite ||
12025         gameMode == MachinePlaysBlack ||
12026         gameMode == TwoMachinesPlay ||
12027         gameMode == EndOfGame) {
12028         i = forwardMostMove;
12029         while (i > currentMove) {
12030             SendToProgram("undo\n", &first);
12031             i--;
12032         }
12033         whiteTimeRemaining = timeRemaining[0][currentMove];
12034         blackTimeRemaining = timeRemaining[1][currentMove];
12035         DisplayBothClocks();
12036         if (whiteFlag || blackFlag) {
12037             whiteFlag = blackFlag = 0;
12038         }
12039         DisplayTitle("");
12040     }           
12041     
12042     gameMode = EditGame;
12043     ModeHighlight();
12044     SetGameInfo();
12045 }
12046
12047
12048 void
12049 EditPositionEvent()
12050 {
12051     if (gameMode == EditPosition) {
12052         EditGameEvent();
12053         return;
12054     }
12055     
12056     EditGameEvent();
12057     if (gameMode != EditGame) return;
12058     
12059     gameMode = EditPosition;
12060     ModeHighlight();
12061     SetGameInfo();
12062     if (currentMove > 0)
12063       CopyBoard(boards[0], boards[currentMove]);
12064     
12065     blackPlaysFirst = !WhiteOnMove(currentMove);
12066     ResetClocks();
12067     currentMove = forwardMostMove = backwardMostMove = 0;
12068     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12069     DisplayMove(-1);
12070 }
12071
12072 void
12073 ExitAnalyzeMode()
12074 {
12075     /* [DM] icsEngineAnalyze - possible call from other functions */
12076     if (appData.icsEngineAnalyze) {
12077         appData.icsEngineAnalyze = FALSE;
12078
12079         DisplayMessage("",_("Close ICS engine analyze..."));
12080     }
12081     if (first.analysisSupport && first.analyzing) {
12082       SendToProgram("exit\n", &first);
12083       first.analyzing = FALSE;
12084     }
12085     thinkOutput[0] = NULLCHAR;
12086 }
12087
12088 void
12089 EditPositionDone(Boolean fakeRights)
12090 {
12091     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12092
12093     startedFromSetupPosition = TRUE;
12094     InitChessProgram(&first, FALSE);
12095     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12096       boards[0][EP_STATUS] = EP_NONE;
12097       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12098     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12099         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12100         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12101       } else boards[0][CASTLING][2] = NoRights;
12102     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12103         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12104         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12105       } else boards[0][CASTLING][5] = NoRights;
12106     }
12107     SendToProgram("force\n", &first);
12108     if (blackPlaysFirst) {
12109         strcpy(moveList[0], "");
12110         strcpy(parseList[0], "");
12111         currentMove = forwardMostMove = backwardMostMove = 1;
12112         CopyBoard(boards[1], boards[0]);
12113     } else {
12114         currentMove = forwardMostMove = backwardMostMove = 0;
12115     }
12116     SendBoard(&first, forwardMostMove);
12117     if (appData.debugMode) {
12118         fprintf(debugFP, "EditPosDone\n");
12119     }
12120     DisplayTitle("");
12121     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12122     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12123     gameMode = EditGame;
12124     ModeHighlight();
12125     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12126     ClearHighlights(); /* [AS] */
12127 }
12128
12129 /* Pause for `ms' milliseconds */
12130 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12131 void
12132 TimeDelay(ms)
12133      long ms;
12134 {
12135     TimeMark m1, m2;
12136
12137     GetTimeMark(&m1);
12138     do {
12139         GetTimeMark(&m2);
12140     } while (SubtractTimeMarks(&m2, &m1) < ms);
12141 }
12142
12143 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12144 void
12145 SendMultiLineToICS(buf)
12146      char *buf;
12147 {
12148     char temp[MSG_SIZ+1], *p;
12149     int len;
12150
12151     len = strlen(buf);
12152     if (len > MSG_SIZ)
12153       len = MSG_SIZ;
12154   
12155     strncpy(temp, buf, len);
12156     temp[len] = 0;
12157
12158     p = temp;
12159     while (*p) {
12160         if (*p == '\n' || *p == '\r')
12161           *p = ' ';
12162         ++p;
12163     }
12164
12165     strcat(temp, "\n");
12166     SendToICS(temp);
12167     SendToPlayer(temp, strlen(temp));
12168 }
12169
12170 void
12171 SetWhiteToPlayEvent()
12172 {
12173     if (gameMode == EditPosition) {
12174         blackPlaysFirst = FALSE;
12175         DisplayBothClocks();    /* works because currentMove is 0 */
12176     } else if (gameMode == IcsExamining) {
12177         SendToICS(ics_prefix);
12178         SendToICS("tomove white\n");
12179     }
12180 }
12181
12182 void
12183 SetBlackToPlayEvent()
12184 {
12185     if (gameMode == EditPosition) {
12186         blackPlaysFirst = TRUE;
12187         currentMove = 1;        /* kludge */
12188         DisplayBothClocks();
12189         currentMove = 0;
12190     } else if (gameMode == IcsExamining) {
12191         SendToICS(ics_prefix);
12192         SendToICS("tomove black\n");
12193     }
12194 }
12195
12196 void
12197 EditPositionMenuEvent(selection, x, y)
12198      ChessSquare selection;
12199      int x, y;
12200 {
12201     char buf[MSG_SIZ];
12202     ChessSquare piece = boards[0][y][x];
12203
12204     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12205
12206     switch (selection) {
12207       case ClearBoard:
12208         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12209             SendToICS(ics_prefix);
12210             SendToICS("bsetup clear\n");
12211         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12212             SendToICS(ics_prefix);
12213             SendToICS("clearboard\n");
12214         } else {
12215             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12216                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12217                 for (y = 0; y < BOARD_HEIGHT; y++) {
12218                     if (gameMode == IcsExamining) {
12219                         if (boards[currentMove][y][x] != EmptySquare) {
12220                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
12221                                     AAA + x, ONE + y);
12222                             SendToICS(buf);
12223                         }
12224                     } else {
12225                         boards[0][y][x] = p;
12226                     }
12227                 }
12228             }
12229         }
12230         if (gameMode == EditPosition) {
12231             DrawPosition(FALSE, boards[0]);
12232         }
12233         break;
12234
12235       case WhitePlay:
12236         SetWhiteToPlayEvent();
12237         break;
12238
12239       case BlackPlay:
12240         SetBlackToPlayEvent();
12241         break;
12242
12243       case EmptySquare:
12244         if (gameMode == IcsExamining) {
12245             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12246             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12247             SendToICS(buf);
12248         } else {
12249             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12250                 if(x == BOARD_LEFT-2) {
12251                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12252                     boards[0][y][1] = 0;
12253                 } else
12254                 if(x == BOARD_RGHT+1) {
12255                     if(y >= gameInfo.holdingsSize) break;
12256                     boards[0][y][BOARD_WIDTH-2] = 0;
12257                 } else break;
12258             }
12259             boards[0][y][x] = EmptySquare;
12260             DrawPosition(FALSE, boards[0]);
12261         }
12262         break;
12263
12264       case PromotePiece:
12265         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12266            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12267             selection = (ChessSquare) (PROMOTED piece);
12268         } else if(piece == EmptySquare) selection = WhiteSilver;
12269         else selection = (ChessSquare)((int)piece - 1);
12270         goto defaultlabel;
12271
12272       case DemotePiece:
12273         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12274            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12275             selection = (ChessSquare) (DEMOTED piece);
12276         } else if(piece == EmptySquare) selection = BlackSilver;
12277         else selection = (ChessSquare)((int)piece + 1);       
12278         goto defaultlabel;
12279
12280       case WhiteQueen:
12281       case BlackQueen:
12282         if(gameInfo.variant == VariantShatranj ||
12283            gameInfo.variant == VariantXiangqi  ||
12284            gameInfo.variant == VariantCourier  ||
12285            gameInfo.variant == VariantMakruk     )
12286             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12287         goto defaultlabel;
12288
12289       case WhiteKing:
12290       case BlackKing:
12291         if(gameInfo.variant == VariantXiangqi)
12292             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12293         if(gameInfo.variant == VariantKnightmate)
12294             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12295       default:
12296         defaultlabel:
12297         if (gameMode == IcsExamining) {
12298             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12299             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12300                     PieceToChar(selection), AAA + x, ONE + y);
12301             SendToICS(buf);
12302         } else {
12303             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12304                 int n;
12305                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12306                     n = PieceToNumber(selection - BlackPawn);
12307                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12308                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12309                     boards[0][BOARD_HEIGHT-1-n][1]++;
12310                 } else
12311                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12312                     n = PieceToNumber(selection);
12313                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12314                     boards[0][n][BOARD_WIDTH-1] = selection;
12315                     boards[0][n][BOARD_WIDTH-2]++;
12316                 }
12317             } else
12318             boards[0][y][x] = selection;
12319             DrawPosition(TRUE, boards[0]);
12320         }
12321         break;
12322     }
12323 }
12324
12325
12326 void
12327 DropMenuEvent(selection, x, y)
12328      ChessSquare selection;
12329      int x, y;
12330 {
12331     ChessMove moveType;
12332
12333     switch (gameMode) {
12334       case IcsPlayingWhite:
12335       case MachinePlaysBlack:
12336         if (!WhiteOnMove(currentMove)) {
12337             DisplayMoveError(_("It is Black's turn"));
12338             return;
12339         }
12340         moveType = WhiteDrop;
12341         break;
12342       case IcsPlayingBlack:
12343       case MachinePlaysWhite:
12344         if (WhiteOnMove(currentMove)) {
12345             DisplayMoveError(_("It is White's turn"));
12346             return;
12347         }
12348         moveType = BlackDrop;
12349         break;
12350       case EditGame:
12351         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12352         break;
12353       default:
12354         return;
12355     }
12356
12357     if (moveType == BlackDrop && selection < BlackPawn) {
12358       selection = (ChessSquare) ((int) selection
12359                                  + (int) BlackPawn - (int) WhitePawn);
12360     }
12361     if (boards[currentMove][y][x] != EmptySquare) {
12362         DisplayMoveError(_("That square is occupied"));
12363         return;
12364     }
12365
12366     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12367 }
12368
12369 void
12370 AcceptEvent()
12371 {
12372     /* Accept a pending offer of any kind from opponent */
12373     
12374     if (appData.icsActive) {
12375         SendToICS(ics_prefix);
12376         SendToICS("accept\n");
12377     } else if (cmailMsgLoaded) {
12378         if (currentMove == cmailOldMove &&
12379             commentList[cmailOldMove] != NULL &&
12380             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12381                    "Black offers a draw" : "White offers a draw")) {
12382             TruncateGame();
12383             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12384             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12385         } else {
12386             DisplayError(_("There is no pending offer on this move"), 0);
12387             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12388         }
12389     } else {
12390         /* Not used for offers from chess program */
12391     }
12392 }
12393
12394 void
12395 DeclineEvent()
12396 {
12397     /* Decline a pending offer of any kind from opponent */
12398     
12399     if (appData.icsActive) {
12400         SendToICS(ics_prefix);
12401         SendToICS("decline\n");
12402     } else if (cmailMsgLoaded) {
12403         if (currentMove == cmailOldMove &&
12404             commentList[cmailOldMove] != NULL &&
12405             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12406                    "Black offers a draw" : "White offers a draw")) {
12407 #ifdef NOTDEF
12408             AppendComment(cmailOldMove, "Draw declined", TRUE);
12409             DisplayComment(cmailOldMove - 1, "Draw declined");
12410 #endif /*NOTDEF*/
12411         } else {
12412             DisplayError(_("There is no pending offer on this move"), 0);
12413         }
12414     } else {
12415         /* Not used for offers from chess program */
12416     }
12417 }
12418
12419 void
12420 RematchEvent()
12421 {
12422     /* Issue ICS rematch command */
12423     if (appData.icsActive) {
12424         SendToICS(ics_prefix);
12425         SendToICS("rematch\n");
12426     }
12427 }
12428
12429 void
12430 CallFlagEvent()
12431 {
12432     /* Call your opponent's flag (claim a win on time) */
12433     if (appData.icsActive) {
12434         SendToICS(ics_prefix);
12435         SendToICS("flag\n");
12436     } else {
12437         switch (gameMode) {
12438           default:
12439             return;
12440           case MachinePlaysWhite:
12441             if (whiteFlag) {
12442                 if (blackFlag)
12443                   GameEnds(GameIsDrawn, "Both players ran out of time",
12444                            GE_PLAYER);
12445                 else
12446                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12447             } else {
12448                 DisplayError(_("Your opponent is not out of time"), 0);
12449             }
12450             break;
12451           case MachinePlaysBlack:
12452             if (blackFlag) {
12453                 if (whiteFlag)
12454                   GameEnds(GameIsDrawn, "Both players ran out of time",
12455                            GE_PLAYER);
12456                 else
12457                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12458             } else {
12459                 DisplayError(_("Your opponent is not out of time"), 0);
12460             }
12461             break;
12462         }
12463     }
12464 }
12465
12466 void
12467 DrawEvent()
12468 {
12469     /* Offer draw or accept pending draw offer from opponent */
12470     
12471     if (appData.icsActive) {
12472         /* Note: tournament rules require draw offers to be
12473            made after you make your move but before you punch
12474            your clock.  Currently ICS doesn't let you do that;
12475            instead, you immediately punch your clock after making
12476            a move, but you can offer a draw at any time. */
12477         
12478         SendToICS(ics_prefix);
12479         SendToICS("draw\n");
12480         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12481     } else if (cmailMsgLoaded) {
12482         if (currentMove == cmailOldMove &&
12483             commentList[cmailOldMove] != NULL &&
12484             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12485                    "Black offers a draw" : "White offers a draw")) {
12486             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12487             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12488         } else if (currentMove == cmailOldMove + 1) {
12489             char *offer = WhiteOnMove(cmailOldMove) ?
12490               "White offers a draw" : "Black offers a draw";
12491             AppendComment(currentMove, offer, TRUE);
12492             DisplayComment(currentMove - 1, offer);
12493             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12494         } else {
12495             DisplayError(_("You must make your move before offering a draw"), 0);
12496             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12497         }
12498     } else if (first.offeredDraw) {
12499         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12500     } else {
12501         if (first.sendDrawOffers) {
12502             SendToProgram("draw\n", &first);
12503             userOfferedDraw = TRUE;
12504         }
12505     }
12506 }
12507
12508 void
12509 AdjournEvent()
12510 {
12511     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12512     
12513     if (appData.icsActive) {
12514         SendToICS(ics_prefix);
12515         SendToICS("adjourn\n");
12516     } else {
12517         /* Currently GNU Chess doesn't offer or accept Adjourns */
12518     }
12519 }
12520
12521
12522 void
12523 AbortEvent()
12524 {
12525     /* Offer Abort or accept pending Abort offer from opponent */
12526     
12527     if (appData.icsActive) {
12528         SendToICS(ics_prefix);
12529         SendToICS("abort\n");
12530     } else {
12531         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12532     }
12533 }
12534
12535 void
12536 ResignEvent()
12537 {
12538     /* Resign.  You can do this even if it's not your turn. */
12539     
12540     if (appData.icsActive) {
12541         SendToICS(ics_prefix);
12542         SendToICS("resign\n");
12543     } else {
12544         switch (gameMode) {
12545           case MachinePlaysWhite:
12546             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12547             break;
12548           case MachinePlaysBlack:
12549             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12550             break;
12551           case EditGame:
12552             if (cmailMsgLoaded) {
12553                 TruncateGame();
12554                 if (WhiteOnMove(cmailOldMove)) {
12555                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12556                 } else {
12557                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12558                 }
12559                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12560             }
12561             break;
12562           default:
12563             break;
12564         }
12565     }
12566 }
12567
12568
12569 void
12570 StopObservingEvent()
12571 {
12572     /* Stop observing current games */
12573     SendToICS(ics_prefix);
12574     SendToICS("unobserve\n");
12575 }
12576
12577 void
12578 StopExaminingEvent()
12579 {
12580     /* Stop observing current game */
12581     SendToICS(ics_prefix);
12582     SendToICS("unexamine\n");
12583 }
12584
12585 void
12586 ForwardInner(target)
12587      int target;
12588 {
12589     int limit;
12590
12591     if (appData.debugMode)
12592         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12593                 target, currentMove, forwardMostMove);
12594
12595     if (gameMode == EditPosition)
12596       return;
12597
12598     if (gameMode == PlayFromGameFile && !pausing)
12599       PauseEvent();
12600     
12601     if (gameMode == IcsExamining && pausing)
12602       limit = pauseExamForwardMostMove;
12603     else
12604       limit = forwardMostMove;
12605     
12606     if (target > limit) target = limit;
12607
12608     if (target > 0 && moveList[target - 1][0]) {
12609         int fromX, fromY, toX, toY;
12610         toX = moveList[target - 1][2] - AAA;
12611         toY = moveList[target - 1][3] - ONE;
12612         if (moveList[target - 1][1] == '@') {
12613             if (appData.highlightLastMove) {
12614                 SetHighlights(-1, -1, toX, toY);
12615             }
12616         } else {
12617             fromX = moveList[target - 1][0] - AAA;
12618             fromY = moveList[target - 1][1] - ONE;
12619             if (target == currentMove + 1) {
12620                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12621             }
12622             if (appData.highlightLastMove) {
12623                 SetHighlights(fromX, fromY, toX, toY);
12624             }
12625         }
12626     }
12627     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12628         gameMode == Training || gameMode == PlayFromGameFile || 
12629         gameMode == AnalyzeFile) {
12630         while (currentMove < target) {
12631             SendMoveToProgram(currentMove++, &first);
12632         }
12633     } else {
12634         currentMove = target;
12635     }
12636     
12637     if (gameMode == EditGame || gameMode == EndOfGame) {
12638         whiteTimeRemaining = timeRemaining[0][currentMove];
12639         blackTimeRemaining = timeRemaining[1][currentMove];
12640     }
12641     DisplayBothClocks();
12642     DisplayMove(currentMove - 1);
12643     DrawPosition(FALSE, boards[currentMove]);
12644     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12645     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12646         DisplayComment(currentMove - 1, commentList[currentMove]);
12647     }
12648 }
12649
12650
12651 void
12652 ForwardEvent()
12653 {
12654     if (gameMode == IcsExamining && !pausing) {
12655         SendToICS(ics_prefix);
12656         SendToICS("forward\n");
12657     } else {
12658         ForwardInner(currentMove + 1);
12659     }
12660 }
12661
12662 void
12663 ToEndEvent()
12664 {
12665     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12666         /* to optimze, we temporarily turn off analysis mode while we feed
12667          * the remaining moves to the engine. Otherwise we get analysis output
12668          * after each move.
12669          */ 
12670         if (first.analysisSupport) {
12671           SendToProgram("exit\nforce\n", &first);
12672           first.analyzing = FALSE;
12673         }
12674     }
12675         
12676     if (gameMode == IcsExamining && !pausing) {
12677         SendToICS(ics_prefix);
12678         SendToICS("forward 999999\n");
12679     } else {
12680         ForwardInner(forwardMostMove);
12681     }
12682
12683     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12684         /* we have fed all the moves, so reactivate analysis mode */
12685         SendToProgram("analyze\n", &first);
12686         first.analyzing = TRUE;
12687         /*first.maybeThinking = TRUE;*/
12688         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12689     }
12690 }
12691
12692 void
12693 BackwardInner(target)
12694      int target;
12695 {
12696     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12697
12698     if (appData.debugMode)
12699         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12700                 target, currentMove, forwardMostMove);
12701
12702     if (gameMode == EditPosition) return;
12703     if (currentMove <= backwardMostMove) {
12704         ClearHighlights();
12705         DrawPosition(full_redraw, boards[currentMove]);
12706         return;
12707     }
12708     if (gameMode == PlayFromGameFile && !pausing)
12709       PauseEvent();
12710     
12711     if (moveList[target][0]) {
12712         int fromX, fromY, toX, toY;
12713         toX = moveList[target][2] - AAA;
12714         toY = moveList[target][3] - ONE;
12715         if (moveList[target][1] == '@') {
12716             if (appData.highlightLastMove) {
12717                 SetHighlights(-1, -1, toX, toY);
12718             }
12719         } else {
12720             fromX = moveList[target][0] - AAA;
12721             fromY = moveList[target][1] - ONE;
12722             if (target == currentMove - 1) {
12723                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12724             }
12725             if (appData.highlightLastMove) {
12726                 SetHighlights(fromX, fromY, toX, toY);
12727             }
12728         }
12729     }
12730     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12731         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12732         while (currentMove > target) {
12733             SendToProgram("undo\n", &first);
12734             currentMove--;
12735         }
12736     } else {
12737         currentMove = target;
12738     }
12739     
12740     if (gameMode == EditGame || gameMode == EndOfGame) {
12741         whiteTimeRemaining = timeRemaining[0][currentMove];
12742         blackTimeRemaining = timeRemaining[1][currentMove];
12743     }
12744     DisplayBothClocks();
12745     DisplayMove(currentMove - 1);
12746     DrawPosition(full_redraw, boards[currentMove]);
12747     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12748     // [HGM] PV info: routine tests if comment empty
12749     DisplayComment(currentMove - 1, commentList[currentMove]);
12750 }
12751
12752 void
12753 BackwardEvent()
12754 {
12755     if (gameMode == IcsExamining && !pausing) {
12756         SendToICS(ics_prefix);
12757         SendToICS("backward\n");
12758     } else {
12759         BackwardInner(currentMove - 1);
12760     }
12761 }
12762
12763 void
12764 ToStartEvent()
12765 {
12766     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12767         /* to optimize, we temporarily turn off analysis mode while we undo
12768          * all the moves. Otherwise we get analysis output after each undo.
12769          */ 
12770         if (first.analysisSupport) {
12771           SendToProgram("exit\nforce\n", &first);
12772           first.analyzing = FALSE;
12773         }
12774     }
12775
12776     if (gameMode == IcsExamining && !pausing) {
12777         SendToICS(ics_prefix);
12778         SendToICS("backward 999999\n");
12779     } else {
12780         BackwardInner(backwardMostMove);
12781     }
12782
12783     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12784         /* we have fed all the moves, so reactivate analysis mode */
12785         SendToProgram("analyze\n", &first);
12786         first.analyzing = TRUE;
12787         /*first.maybeThinking = TRUE;*/
12788         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12789     }
12790 }
12791
12792 void
12793 ToNrEvent(int to)
12794 {
12795   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12796   if (to >= forwardMostMove) to = forwardMostMove;
12797   if (to <= backwardMostMove) to = backwardMostMove;
12798   if (to < currentMove) {
12799     BackwardInner(to);
12800   } else {
12801     ForwardInner(to);
12802   }
12803 }
12804
12805 void
12806 RevertEvent(Boolean annotate)
12807 {
12808     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12809         return;
12810     }
12811     if (gameMode != IcsExamining) {
12812         DisplayError(_("You are not examining a game"), 0);
12813         return;
12814     }
12815     if (pausing) {
12816         DisplayError(_("You can't revert while pausing"), 0);
12817         return;
12818     }
12819     SendToICS(ics_prefix);
12820     SendToICS("revert\n");
12821 }
12822
12823 void
12824 RetractMoveEvent()
12825 {
12826     switch (gameMode) {
12827       case MachinePlaysWhite:
12828       case MachinePlaysBlack:
12829         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12830             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12831             return;
12832         }
12833         if (forwardMostMove < 2) return;
12834         currentMove = forwardMostMove = forwardMostMove - 2;
12835         whiteTimeRemaining = timeRemaining[0][currentMove];
12836         blackTimeRemaining = timeRemaining[1][currentMove];
12837         DisplayBothClocks();
12838         DisplayMove(currentMove - 1);
12839         ClearHighlights();/*!! could figure this out*/
12840         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12841         SendToProgram("remove\n", &first);
12842         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12843         break;
12844
12845       case BeginningOfGame:
12846       default:
12847         break;
12848
12849       case IcsPlayingWhite:
12850       case IcsPlayingBlack:
12851         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12852             SendToICS(ics_prefix);
12853             SendToICS("takeback 2\n");
12854         } else {
12855             SendToICS(ics_prefix);
12856             SendToICS("takeback 1\n");
12857         }
12858         break;
12859     }
12860 }
12861
12862 void
12863 MoveNowEvent()
12864 {
12865     ChessProgramState *cps;
12866
12867     switch (gameMode) {
12868       case MachinePlaysWhite:
12869         if (!WhiteOnMove(forwardMostMove)) {
12870             DisplayError(_("It is your turn"), 0);
12871             return;
12872         }
12873         cps = &first;
12874         break;
12875       case MachinePlaysBlack:
12876         if (WhiteOnMove(forwardMostMove)) {
12877             DisplayError(_("It is your turn"), 0);
12878             return;
12879         }
12880         cps = &first;
12881         break;
12882       case TwoMachinesPlay:
12883         if (WhiteOnMove(forwardMostMove) ==
12884             (first.twoMachinesColor[0] == 'w')) {
12885             cps = &first;
12886         } else {
12887             cps = &second;
12888         }
12889         break;
12890       case BeginningOfGame:
12891       default:
12892         return;
12893     }
12894     SendToProgram("?\n", cps);
12895 }
12896
12897 void
12898 TruncateGameEvent()
12899 {
12900     EditGameEvent();
12901     if (gameMode != EditGame) return;
12902     TruncateGame();
12903 }
12904
12905 void
12906 TruncateGame()
12907 {
12908     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12909     if (forwardMostMove > currentMove) {
12910         if (gameInfo.resultDetails != NULL) {
12911             free(gameInfo.resultDetails);
12912             gameInfo.resultDetails = NULL;
12913             gameInfo.result = GameUnfinished;
12914         }
12915         forwardMostMove = currentMove;
12916         HistorySet(parseList, backwardMostMove, forwardMostMove,
12917                    currentMove-1);
12918     }
12919 }
12920
12921 void
12922 HintEvent()
12923 {
12924     if (appData.noChessProgram) return;
12925     switch (gameMode) {
12926       case MachinePlaysWhite:
12927         if (WhiteOnMove(forwardMostMove)) {
12928             DisplayError(_("Wait until your turn"), 0);
12929             return;
12930         }
12931         break;
12932       case BeginningOfGame:
12933       case MachinePlaysBlack:
12934         if (!WhiteOnMove(forwardMostMove)) {
12935             DisplayError(_("Wait until your turn"), 0);
12936             return;
12937         }
12938         break;
12939       default:
12940         DisplayError(_("No hint available"), 0);
12941         return;
12942     }
12943     SendToProgram("hint\n", &first);
12944     hintRequested = TRUE;
12945 }
12946
12947 void
12948 BookEvent()
12949 {
12950     if (appData.noChessProgram) return;
12951     switch (gameMode) {
12952       case MachinePlaysWhite:
12953         if (WhiteOnMove(forwardMostMove)) {
12954             DisplayError(_("Wait until your turn"), 0);
12955             return;
12956         }
12957         break;
12958       case BeginningOfGame:
12959       case MachinePlaysBlack:
12960         if (!WhiteOnMove(forwardMostMove)) {
12961             DisplayError(_("Wait until your turn"), 0);
12962             return;
12963         }
12964         break;
12965       case EditPosition:
12966         EditPositionDone(TRUE);
12967         break;
12968       case TwoMachinesPlay:
12969         return;
12970       default:
12971         break;
12972     }
12973     SendToProgram("bk\n", &first);
12974     bookOutput[0] = NULLCHAR;
12975     bookRequested = TRUE;
12976 }
12977
12978 void
12979 AboutGameEvent()
12980 {
12981     char *tags = PGNTags(&gameInfo);
12982     TagsPopUp(tags, CmailMsg());
12983     free(tags);
12984 }
12985
12986 /* end button procedures */
12987
12988 void
12989 PrintPosition(fp, move)
12990      FILE *fp;
12991      int move;
12992 {
12993     int i, j;
12994     
12995     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12996         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12997             char c = PieceToChar(boards[move][i][j]);
12998             fputc(c == 'x' ? '.' : c, fp);
12999             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13000         }
13001     }
13002     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13003       fprintf(fp, "white to play\n");
13004     else
13005       fprintf(fp, "black to play\n");
13006 }
13007
13008 void
13009 PrintOpponents(fp)
13010      FILE *fp;
13011 {
13012     if (gameInfo.white != NULL) {
13013         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13014     } else {
13015         fprintf(fp, "\n");
13016     }
13017 }
13018
13019 /* Find last component of program's own name, using some heuristics */
13020 void
13021 TidyProgramName(prog, host, buf)
13022      char *prog, *host, buf[MSG_SIZ];
13023 {
13024     char *p, *q;
13025     int local = (strcmp(host, "localhost") == 0);
13026     while (!local && (p = strchr(prog, ';')) != NULL) {
13027         p++;
13028         while (*p == ' ') p++;
13029         prog = p;
13030     }
13031     if (*prog == '"' || *prog == '\'') {
13032         q = strchr(prog + 1, *prog);
13033     } else {
13034         q = strchr(prog, ' ');
13035     }
13036     if (q == NULL) q = prog + strlen(prog);
13037     p = q;
13038     while (p >= prog && *p != '/' && *p != '\\') p--;
13039     p++;
13040     if(p == prog && *p == '"') p++;
13041     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13042     memcpy(buf, p, q - p);
13043     buf[q - p] = NULLCHAR;
13044     if (!local) {
13045         strcat(buf, "@");
13046         strcat(buf, host);
13047     }
13048 }
13049
13050 char *
13051 TimeControlTagValue()
13052 {
13053     char buf[MSG_SIZ];
13054     if (!appData.clockMode) {
13055         strcpy(buf, "-");
13056     } else if (movesPerSession > 0) {
13057         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
13058     } else if (timeIncrement == 0) {
13059         sprintf(buf, "%ld", timeControl/1000);
13060     } else {
13061         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13062     }
13063     return StrSave(buf);
13064 }
13065
13066 void
13067 SetGameInfo()
13068 {
13069     /* This routine is used only for certain modes */
13070     VariantClass v = gameInfo.variant;
13071     ChessMove r = GameUnfinished;
13072     char *p = NULL;
13073
13074     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13075         r = gameInfo.result; 
13076         p = gameInfo.resultDetails; 
13077         gameInfo.resultDetails = NULL;
13078     }
13079     ClearGameInfo(&gameInfo);
13080     gameInfo.variant = v;
13081
13082     switch (gameMode) {
13083       case MachinePlaysWhite:
13084         gameInfo.event = StrSave( appData.pgnEventHeader );
13085         gameInfo.site = StrSave(HostName());
13086         gameInfo.date = PGNDate();
13087         gameInfo.round = StrSave("-");
13088         gameInfo.white = StrSave(first.tidy);
13089         gameInfo.black = StrSave(UserName());
13090         gameInfo.timeControl = TimeControlTagValue();
13091         break;
13092
13093       case MachinePlaysBlack:
13094         gameInfo.event = StrSave( appData.pgnEventHeader );
13095         gameInfo.site = StrSave(HostName());
13096         gameInfo.date = PGNDate();
13097         gameInfo.round = StrSave("-");
13098         gameInfo.white = StrSave(UserName());
13099         gameInfo.black = StrSave(first.tidy);
13100         gameInfo.timeControl = TimeControlTagValue();
13101         break;
13102
13103       case TwoMachinesPlay:
13104         gameInfo.event = StrSave( appData.pgnEventHeader );
13105         gameInfo.site = StrSave(HostName());
13106         gameInfo.date = PGNDate();
13107         if (matchGame > 0) {
13108             char buf[MSG_SIZ];
13109             sprintf(buf, "%d", matchGame);
13110             gameInfo.round = StrSave(buf);
13111         } else {
13112             gameInfo.round = StrSave("-");
13113         }
13114         if (first.twoMachinesColor[0] == 'w') {
13115             gameInfo.white = StrSave(first.tidy);
13116             gameInfo.black = StrSave(second.tidy);
13117         } else {
13118             gameInfo.white = StrSave(second.tidy);
13119             gameInfo.black = StrSave(first.tidy);
13120         }
13121         gameInfo.timeControl = TimeControlTagValue();
13122         break;
13123
13124       case EditGame:
13125         gameInfo.event = StrSave("Edited game");
13126         gameInfo.site = StrSave(HostName());
13127         gameInfo.date = PGNDate();
13128         gameInfo.round = StrSave("-");
13129         gameInfo.white = StrSave("-");
13130         gameInfo.black = StrSave("-");
13131         gameInfo.result = r;
13132         gameInfo.resultDetails = p;
13133         break;
13134
13135       case EditPosition:
13136         gameInfo.event = StrSave("Edited position");
13137         gameInfo.site = StrSave(HostName());
13138         gameInfo.date = PGNDate();
13139         gameInfo.round = StrSave("-");
13140         gameInfo.white = StrSave("-");
13141         gameInfo.black = StrSave("-");
13142         break;
13143
13144       case IcsPlayingWhite:
13145       case IcsPlayingBlack:
13146       case IcsObserving:
13147       case IcsExamining:
13148         break;
13149
13150       case PlayFromGameFile:
13151         gameInfo.event = StrSave("Game from non-PGN file");
13152         gameInfo.site = StrSave(HostName());
13153         gameInfo.date = PGNDate();
13154         gameInfo.round = StrSave("-");
13155         gameInfo.white = StrSave("?");
13156         gameInfo.black = StrSave("?");
13157         break;
13158
13159       default:
13160         break;
13161     }
13162 }
13163
13164 void
13165 ReplaceComment(index, text)
13166      int index;
13167      char *text;
13168 {
13169     int len;
13170
13171     while (*text == '\n') text++;
13172     len = strlen(text);
13173     while (len > 0 && text[len - 1] == '\n') len--;
13174
13175     if (commentList[index] != NULL)
13176       free(commentList[index]);
13177
13178     if (len == 0) {
13179         commentList[index] = NULL;
13180         return;
13181     }
13182   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13183       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13184       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13185     commentList[index] = (char *) malloc(len + 2);
13186     strncpy(commentList[index], text, len);
13187     commentList[index][len] = '\n';
13188     commentList[index][len + 1] = NULLCHAR;
13189   } else { 
13190     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13191     char *p;
13192     commentList[index] = (char *) malloc(len + 6);
13193     strcpy(commentList[index], "{\n");
13194     strncpy(commentList[index]+2, text, len);
13195     commentList[index][len+2] = NULLCHAR;
13196     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13197     strcat(commentList[index], "\n}\n");
13198   }
13199 }
13200
13201 void
13202 CrushCRs(text)
13203      char *text;
13204 {
13205   char *p = text;
13206   char *q = text;
13207   char ch;
13208
13209   do {
13210     ch = *p++;
13211     if (ch == '\r') continue;
13212     *q++ = ch;
13213   } while (ch != '\0');
13214 }
13215
13216 void
13217 AppendComment(index, text, addBraces)
13218      int index;
13219      char *text;
13220      Boolean addBraces; // [HGM] braces: tells if we should add {}
13221 {
13222     int oldlen, len;
13223     char *old;
13224
13225 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13226     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13227
13228     CrushCRs(text);
13229     while (*text == '\n') text++;
13230     len = strlen(text);
13231     while (len > 0 && text[len - 1] == '\n') len--;
13232
13233     if (len == 0) return;
13234
13235     if (commentList[index] != NULL) {
13236         old = commentList[index];
13237         oldlen = strlen(old);
13238         while(commentList[index][oldlen-1] ==  '\n')
13239           commentList[index][--oldlen] = NULLCHAR;
13240         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13241         strcpy(commentList[index], old);
13242         free(old);
13243         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13244         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13245           if(addBraces) addBraces = FALSE; else { text++; len--; }
13246           while (*text == '\n') { text++; len--; }
13247           commentList[index][--oldlen] = NULLCHAR;
13248       }
13249         if(addBraces) strcat(commentList[index], "\n{\n");
13250         else          strcat(commentList[index], "\n");
13251         strcat(commentList[index], text);
13252         if(addBraces) strcat(commentList[index], "\n}\n");
13253         else          strcat(commentList[index], "\n");
13254     } else {
13255         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13256         if(addBraces)
13257              strcpy(commentList[index], "{\n");
13258         else commentList[index][0] = NULLCHAR;
13259         strcat(commentList[index], text);
13260         strcat(commentList[index], "\n");
13261         if(addBraces) strcat(commentList[index], "}\n");
13262     }
13263 }
13264
13265 static char * FindStr( char * text, char * sub_text )
13266 {
13267     char * result = strstr( text, sub_text );
13268
13269     if( result != NULL ) {
13270         result += strlen( sub_text );
13271     }
13272
13273     return result;
13274 }
13275
13276 /* [AS] Try to extract PV info from PGN comment */
13277 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13278 char *GetInfoFromComment( int index, char * text )
13279 {
13280     char * sep = text;
13281
13282     if( text != NULL && index > 0 ) {
13283         int score = 0;
13284         int depth = 0;
13285         int time = -1, sec = 0, deci;
13286         char * s_eval = FindStr( text, "[%eval " );
13287         char * s_emt = FindStr( text, "[%emt " );
13288
13289         if( s_eval != NULL || s_emt != NULL ) {
13290             /* New style */
13291             char delim;
13292
13293             if( s_eval != NULL ) {
13294                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13295                     return text;
13296                 }
13297
13298                 if( delim != ']' ) {
13299                     return text;
13300                 }
13301             }
13302
13303             if( s_emt != NULL ) {
13304             }
13305                 return text;
13306         }
13307         else {
13308             /* We expect something like: [+|-]nnn.nn/dd */
13309             int score_lo = 0;
13310
13311             if(*text != '{') return text; // [HGM] braces: must be normal comment
13312
13313             sep = strchr( text, '/' );
13314             if( sep == NULL || sep < (text+4) ) {
13315                 return text;
13316             }
13317
13318             time = -1; sec = -1; deci = -1;
13319             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13320                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13321                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13322                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13323                 return text;
13324             }
13325
13326             if( score_lo < 0 || score_lo >= 100 ) {
13327                 return text;
13328             }
13329
13330             if(sec >= 0) time = 600*time + 10*sec; else
13331             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13332
13333             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13334
13335             /* [HGM] PV time: now locate end of PV info */
13336             while( *++sep >= '0' && *sep <= '9'); // strip depth
13337             if(time >= 0)
13338             while( *++sep >= '0' && *sep <= '9'); // strip time
13339             if(sec >= 0)
13340             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13341             if(deci >= 0)
13342             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13343             while(*sep == ' ') sep++;
13344         }
13345
13346         if( depth <= 0 ) {
13347             return text;
13348         }
13349
13350         if( time < 0 ) {
13351             time = -1;
13352         }
13353
13354         pvInfoList[index-1].depth = depth;
13355         pvInfoList[index-1].score = score;
13356         pvInfoList[index-1].time  = 10*time; // centi-sec
13357         if(*sep == '}') *sep = 0; else *--sep = '{';
13358     }
13359     return sep;
13360 }
13361
13362 void
13363 SendToProgram(message, cps)
13364      char *message;
13365      ChessProgramState *cps;
13366 {
13367     int count, outCount, error;
13368     char buf[MSG_SIZ];
13369
13370     if (cps->pr == NULL) return;
13371     Attention(cps);
13372     
13373     if (appData.debugMode) {
13374         TimeMark now;
13375         GetTimeMark(&now);
13376         fprintf(debugFP, "%ld >%-6s: %s", 
13377                 SubtractTimeMarks(&now, &programStartTime),
13378                 cps->which, message);
13379     }
13380     
13381     count = strlen(message);
13382     outCount = OutputToProcess(cps->pr, message, count, &error);
13383     if (outCount < count && !exiting 
13384                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13385         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13386         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13387             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13388                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13389                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13390             } else {
13391                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13392             }
13393             gameInfo.resultDetails = StrSave(buf);
13394         }
13395         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13396     }
13397 }
13398
13399 void
13400 ReceiveFromProgram(isr, closure, message, count, error)
13401      InputSourceRef isr;
13402      VOIDSTAR closure;
13403      char *message;
13404      int count;
13405      int error;
13406 {
13407     char *end_str;
13408     char buf[MSG_SIZ];
13409     ChessProgramState *cps = (ChessProgramState *)closure;
13410
13411     if (isr != cps->isr) return; /* Killed intentionally */
13412     if (count <= 0) {
13413         if (count == 0) {
13414             sprintf(buf,
13415                     _("Error: %s chess program (%s) exited unexpectedly"),
13416                     cps->which, cps->program);
13417         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13418                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13419                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13420                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13421                 } else {
13422                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13423                 }
13424                 gameInfo.resultDetails = StrSave(buf);
13425             }
13426             RemoveInputSource(cps->isr);
13427             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13428         } else {
13429             sprintf(buf,
13430                     _("Error reading from %s chess program (%s)"),
13431                     cps->which, cps->program);
13432             RemoveInputSource(cps->isr);
13433
13434             /* [AS] Program is misbehaving badly... kill it */
13435             if( count == -2 ) {
13436                 DestroyChildProcess( cps->pr, 9 );
13437                 cps->pr = NoProc;
13438             }
13439
13440             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13441         }
13442         return;
13443     }
13444     
13445     if ((end_str = strchr(message, '\r')) != NULL)
13446       *end_str = NULLCHAR;
13447     if ((end_str = strchr(message, '\n')) != NULL)
13448       *end_str = NULLCHAR;
13449     
13450     if (appData.debugMode) {
13451         TimeMark now; int print = 1;
13452         char *quote = ""; char c; int i;
13453
13454         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13455                 char start = message[0];
13456                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13457                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13458                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13459                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13460                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13461                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13462                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13463                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13464                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13465                     print = (appData.engineComments >= 2);
13466                 }
13467                 message[0] = start; // restore original message
13468         }
13469         if(print) {
13470                 GetTimeMark(&now);
13471                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13472                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13473                         quote,
13474                         message);
13475         }
13476     }
13477
13478     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13479     if (appData.icsEngineAnalyze) {
13480         if (strstr(message, "whisper") != NULL ||
13481              strstr(message, "kibitz") != NULL || 
13482             strstr(message, "tellics") != NULL) return;
13483     }
13484
13485     HandleMachineMove(message, cps);
13486 }
13487
13488
13489 void
13490 SendTimeControl(cps, mps, tc, inc, sd, st)
13491      ChessProgramState *cps;
13492      int mps, inc, sd, st;
13493      long tc;
13494 {
13495     char buf[MSG_SIZ];
13496     int seconds;
13497
13498     if( timeControl_2 > 0 ) {
13499         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13500             tc = timeControl_2;
13501         }
13502     }
13503     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13504     inc /= cps->timeOdds;
13505     st  /= cps->timeOdds;
13506
13507     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13508
13509     if (st > 0) {
13510       /* Set exact time per move, normally using st command */
13511       if (cps->stKludge) {
13512         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13513         seconds = st % 60;
13514         if (seconds == 0) {
13515           sprintf(buf, "level 1 %d\n", st/60);
13516         } else {
13517           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13518         }
13519       } else {
13520         sprintf(buf, "st %d\n", st);
13521       }
13522     } else {
13523       /* Set conventional or incremental time control, using level command */
13524       if (seconds == 0) {
13525         /* Note old gnuchess bug -- minutes:seconds used to not work.
13526            Fixed in later versions, but still avoid :seconds
13527            when seconds is 0. */
13528         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13529       } else {
13530         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13531                 seconds, inc/1000);
13532       }
13533     }
13534     SendToProgram(buf, cps);
13535
13536     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13537     /* Orthogonally, limit search to given depth */
13538     if (sd > 0) {
13539       if (cps->sdKludge) {
13540         sprintf(buf, "depth\n%d\n", sd);
13541       } else {
13542         sprintf(buf, "sd %d\n", sd);
13543       }
13544       SendToProgram(buf, cps);
13545     }
13546
13547     if(cps->nps > 0) { /* [HGM] nps */
13548         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13549         else {
13550                 sprintf(buf, "nps %d\n", cps->nps);
13551               SendToProgram(buf, cps);
13552         }
13553     }
13554 }
13555
13556 ChessProgramState *WhitePlayer()
13557 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13558 {
13559     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13560        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13561         return &second;
13562     return &first;
13563 }
13564
13565 void
13566 SendTimeRemaining(cps, machineWhite)
13567      ChessProgramState *cps;
13568      int /*boolean*/ machineWhite;
13569 {
13570     char message[MSG_SIZ];
13571     long time, otime;
13572
13573     /* Note: this routine must be called when the clocks are stopped
13574        or when they have *just* been set or switched; otherwise
13575        it will be off by the time since the current tick started.
13576     */
13577     if (machineWhite) {
13578         time = whiteTimeRemaining / 10;
13579         otime = blackTimeRemaining / 10;
13580     } else {
13581         time = blackTimeRemaining / 10;
13582         otime = whiteTimeRemaining / 10;
13583     }
13584     /* [HGM] translate opponent's time by time-odds factor */
13585     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13586     if (appData.debugMode) {
13587         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13588     }
13589
13590     if (time <= 0) time = 1;
13591     if (otime <= 0) otime = 1;
13592     
13593     sprintf(message, "time %ld\n", time);
13594     SendToProgram(message, cps);
13595
13596     sprintf(message, "otim %ld\n", otime);
13597     SendToProgram(message, cps);
13598 }
13599
13600 int
13601 BoolFeature(p, name, loc, cps)
13602      char **p;
13603      char *name;
13604      int *loc;
13605      ChessProgramState *cps;
13606 {
13607   char buf[MSG_SIZ];
13608   int len = strlen(name);
13609   int val;
13610   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13611     (*p) += len + 1;
13612     sscanf(*p, "%d", &val);
13613     *loc = (val != 0);
13614     while (**p && **p != ' ') (*p)++;
13615     sprintf(buf, "accepted %s\n", name);
13616     SendToProgram(buf, cps);
13617     return TRUE;
13618   }
13619   return FALSE;
13620 }
13621
13622 int
13623 IntFeature(p, name, loc, cps)
13624      char **p;
13625      char *name;
13626      int *loc;
13627      ChessProgramState *cps;
13628 {
13629   char buf[MSG_SIZ];
13630   int len = strlen(name);
13631   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13632     (*p) += len + 1;
13633     sscanf(*p, "%d", loc);
13634     while (**p && **p != ' ') (*p)++;
13635     sprintf(buf, "accepted %s\n", name);
13636     SendToProgram(buf, cps);
13637     return TRUE;
13638   }
13639   return FALSE;
13640 }
13641
13642 int
13643 StringFeature(p, name, loc, cps)
13644      char **p;
13645      char *name;
13646      char loc[];
13647      ChessProgramState *cps;
13648 {
13649   char buf[MSG_SIZ];
13650   int len = strlen(name);
13651   if (strncmp((*p), name, len) == 0
13652       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13653     (*p) += len + 2;
13654     sscanf(*p, "%[^\"]", loc);
13655     while (**p && **p != '\"') (*p)++;
13656     if (**p == '\"') (*p)++;
13657     sprintf(buf, "accepted %s\n", name);
13658     SendToProgram(buf, cps);
13659     return TRUE;
13660   }
13661   return FALSE;
13662 }
13663
13664 int 
13665 ParseOption(Option *opt, ChessProgramState *cps)
13666 // [HGM] options: process the string that defines an engine option, and determine
13667 // name, type, default value, and allowed value range
13668 {
13669         char *p, *q, buf[MSG_SIZ];
13670         int n, min = (-1)<<31, max = 1<<31, def;
13671
13672         if(p = strstr(opt->name, " -spin ")) {
13673             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13674             if(max < min) max = min; // enforce consistency
13675             if(def < min) def = min;
13676             if(def > max) def = max;
13677             opt->value = def;
13678             opt->min = min;
13679             opt->max = max;
13680             opt->type = Spin;
13681         } else if((p = strstr(opt->name, " -slider "))) {
13682             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13683             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13684             if(max < min) max = min; // enforce consistency
13685             if(def < min) def = min;
13686             if(def > max) def = max;
13687             opt->value = def;
13688             opt->min = min;
13689             opt->max = max;
13690             opt->type = Spin; // Slider;
13691         } else if((p = strstr(opt->name, " -string "))) {
13692             opt->textValue = p+9;
13693             opt->type = TextBox;
13694         } else if((p = strstr(opt->name, " -file "))) {
13695             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13696             opt->textValue = p+7;
13697             opt->type = TextBox; // FileName;
13698         } else if((p = strstr(opt->name, " -path "))) {
13699             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13700             opt->textValue = p+7;
13701             opt->type = TextBox; // PathName;
13702         } else if(p = strstr(opt->name, " -check ")) {
13703             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13704             opt->value = (def != 0);
13705             opt->type = CheckBox;
13706         } else if(p = strstr(opt->name, " -combo ")) {
13707             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13708             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13709             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13710             opt->value = n = 0;
13711             while(q = StrStr(q, " /// ")) {
13712                 n++; *q = 0;    // count choices, and null-terminate each of them
13713                 q += 5;
13714                 if(*q == '*') { // remember default, which is marked with * prefix
13715                     q++;
13716                     opt->value = n;
13717                 }
13718                 cps->comboList[cps->comboCnt++] = q;
13719             }
13720             cps->comboList[cps->comboCnt++] = NULL;
13721             opt->max = n + 1;
13722             opt->type = ComboBox;
13723         } else if(p = strstr(opt->name, " -button")) {
13724             opt->type = Button;
13725         } else if(p = strstr(opt->name, " -save")) {
13726             opt->type = SaveButton;
13727         } else return FALSE;
13728         *p = 0; // terminate option name
13729         // now look if the command-line options define a setting for this engine option.
13730         if(cps->optionSettings && cps->optionSettings[0])
13731             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13732         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13733                 sprintf(buf, "option %s", p);
13734                 if(p = strstr(buf, ",")) *p = 0;
13735                 strcat(buf, "\n");
13736                 SendToProgram(buf, cps);
13737         }
13738         return TRUE;
13739 }
13740
13741 void
13742 FeatureDone(cps, val)
13743      ChessProgramState* cps;
13744      int val;
13745 {
13746   DelayedEventCallback cb = GetDelayedEvent();
13747   if ((cb == InitBackEnd3 && cps == &first) ||
13748       (cb == TwoMachinesEventIfReady && cps == &second)) {
13749     CancelDelayedEvent();
13750     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13751   }
13752   cps->initDone = val;
13753 }
13754
13755 /* Parse feature command from engine */
13756 void
13757 ParseFeatures(args, cps)
13758      char* args;
13759      ChessProgramState *cps;  
13760 {
13761   char *p = args;
13762   char *q;
13763   int val;
13764   char buf[MSG_SIZ];
13765
13766   for (;;) {
13767     while (*p == ' ') p++;
13768     if (*p == NULLCHAR) return;
13769
13770     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13771     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13772     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13773     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13774     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13775     if (BoolFeature(&p, "reuse", &val, cps)) {
13776       /* Engine can disable reuse, but can't enable it if user said no */
13777       if (!val) cps->reuse = FALSE;
13778       continue;
13779     }
13780     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13781     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13782       if (gameMode == TwoMachinesPlay) {
13783         DisplayTwoMachinesTitle();
13784       } else {
13785         DisplayTitle("");
13786       }
13787       continue;
13788     }
13789     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13790     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13791     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13792     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13793     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13794     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13795     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13796     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13797     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13798     if (IntFeature(&p, "done", &val, cps)) {
13799       FeatureDone(cps, val);
13800       continue;
13801     }
13802     /* Added by Tord: */
13803     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13804     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13805     /* End of additions by Tord */
13806
13807     /* [HGM] added features: */
13808     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13809     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13810     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13811     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13812     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13813     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13814     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13815         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13816             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13817             SendToProgram(buf, cps);
13818             continue;
13819         }
13820         if(cps->nrOptions >= MAX_OPTIONS) {
13821             cps->nrOptions--;
13822             sprintf(buf, "%s engine has too many options\n", cps->which);
13823             DisplayError(buf, 0);
13824         }
13825         continue;
13826     }
13827     /* End of additions by HGM */
13828
13829     /* unknown feature: complain and skip */
13830     q = p;
13831     while (*q && *q != '=') q++;
13832     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13833     SendToProgram(buf, cps);
13834     p = q;
13835     if (*p == '=') {
13836       p++;
13837       if (*p == '\"') {
13838         p++;
13839         while (*p && *p != '\"') p++;
13840         if (*p == '\"') p++;
13841       } else {
13842         while (*p && *p != ' ') p++;
13843       }
13844     }
13845   }
13846
13847 }
13848
13849 void
13850 PeriodicUpdatesEvent(newState)
13851      int newState;
13852 {
13853     if (newState == appData.periodicUpdates)
13854       return;
13855
13856     appData.periodicUpdates=newState;
13857
13858     /* Display type changes, so update it now */
13859 //    DisplayAnalysis();
13860
13861     /* Get the ball rolling again... */
13862     if (newState) {
13863         AnalysisPeriodicEvent(1);
13864         StartAnalysisClock();
13865     }
13866 }
13867
13868 void
13869 PonderNextMoveEvent(newState)
13870      int newState;
13871 {
13872     if (newState == appData.ponderNextMove) return;
13873     if (gameMode == EditPosition) EditPositionDone(TRUE);
13874     if (newState) {
13875         SendToProgram("hard\n", &first);
13876         if (gameMode == TwoMachinesPlay) {
13877             SendToProgram("hard\n", &second);
13878         }
13879     } else {
13880         SendToProgram("easy\n", &first);
13881         thinkOutput[0] = NULLCHAR;
13882         if (gameMode == TwoMachinesPlay) {
13883             SendToProgram("easy\n", &second);
13884         }
13885     }
13886     appData.ponderNextMove = newState;
13887 }
13888
13889 void
13890 NewSettingEvent(option, feature, command, value)
13891      char *command;
13892      int option, value, *feature;
13893 {
13894     char buf[MSG_SIZ];
13895
13896     if (gameMode == EditPosition) EditPositionDone(TRUE);
13897     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13898     if(feature == NULL || *feature) SendToProgram(buf, &first);
13899     if (gameMode == TwoMachinesPlay) {
13900         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
13901     }
13902 }
13903
13904 void
13905 ShowThinkingEvent()
13906 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13907 {
13908     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13909     int newState = appData.showThinking
13910         // [HGM] thinking: other features now need thinking output as well
13911         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13912     
13913     if (oldState == newState) return;
13914     oldState = newState;
13915     if (gameMode == EditPosition) EditPositionDone(TRUE);
13916     if (oldState) {
13917         SendToProgram("post\n", &first);
13918         if (gameMode == TwoMachinesPlay) {
13919             SendToProgram("post\n", &second);
13920         }
13921     } else {
13922         SendToProgram("nopost\n", &first);
13923         thinkOutput[0] = NULLCHAR;
13924         if (gameMode == TwoMachinesPlay) {
13925             SendToProgram("nopost\n", &second);
13926         }
13927     }
13928 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13929 }
13930
13931 void
13932 AskQuestionEvent(title, question, replyPrefix, which)
13933      char *title; char *question; char *replyPrefix; char *which;
13934 {
13935   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13936   if (pr == NoProc) return;
13937   AskQuestion(title, question, replyPrefix, pr);
13938 }
13939
13940 void
13941 DisplayMove(moveNumber)
13942      int moveNumber;
13943 {
13944     char message[MSG_SIZ];
13945     char res[MSG_SIZ];
13946     char cpThinkOutput[MSG_SIZ];
13947
13948     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13949     
13950     if (moveNumber == forwardMostMove - 1 || 
13951         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13952
13953         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13954
13955         if (strchr(cpThinkOutput, '\n')) {
13956             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13957         }
13958     } else {
13959         *cpThinkOutput = NULLCHAR;
13960     }
13961
13962     /* [AS] Hide thinking from human user */
13963     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13964         *cpThinkOutput = NULLCHAR;
13965         if( thinkOutput[0] != NULLCHAR ) {
13966             int i;
13967
13968             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13969                 cpThinkOutput[i] = '.';
13970             }
13971             cpThinkOutput[i] = NULLCHAR;
13972             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13973         }
13974     }
13975
13976     if (moveNumber == forwardMostMove - 1 &&
13977         gameInfo.resultDetails != NULL) {
13978         if (gameInfo.resultDetails[0] == NULLCHAR) {
13979             sprintf(res, " %s", PGNResult(gameInfo.result));
13980         } else {
13981             sprintf(res, " {%s} %s",
13982                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
13983         }
13984     } else {
13985         res[0] = NULLCHAR;
13986     }
13987
13988     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13989         DisplayMessage(res, cpThinkOutput);
13990     } else {
13991         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13992                 WhiteOnMove(moveNumber) ? " " : ".. ",
13993                 parseList[moveNumber], res);
13994         DisplayMessage(message, cpThinkOutput);
13995     }
13996 }
13997
13998 void
13999 DisplayComment(moveNumber, text)
14000      int moveNumber;
14001      char *text;
14002 {
14003     char title[MSG_SIZ];
14004     char buf[8000]; // comment can be long!
14005     int score, depth;
14006     
14007     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14008       strcpy(title, "Comment");
14009     } else {
14010       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
14011               WhiteOnMove(moveNumber) ? " " : ".. ",
14012               parseList[moveNumber]);
14013     }
14014     // [HGM] PV info: display PV info together with (or as) comment
14015     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14016       if(text == NULL) text = "";                                           
14017       score = pvInfoList[moveNumber].score;
14018       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14019               depth, (pvInfoList[moveNumber].time+50)/100, text);
14020       text = buf;
14021     }
14022     if (text != NULL && (appData.autoDisplayComment || commentUp))
14023         CommentPopUp(title, text);
14024 }
14025
14026 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14027  * might be busy thinking or pondering.  It can be omitted if your
14028  * gnuchess is configured to stop thinking immediately on any user
14029  * input.  However, that gnuchess feature depends on the FIONREAD
14030  * ioctl, which does not work properly on some flavors of Unix.
14031  */
14032 void
14033 Attention(cps)
14034      ChessProgramState *cps;
14035 {
14036 #if ATTENTION
14037     if (!cps->useSigint) return;
14038     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14039     switch (gameMode) {
14040       case MachinePlaysWhite:
14041       case MachinePlaysBlack:
14042       case TwoMachinesPlay:
14043       case IcsPlayingWhite:
14044       case IcsPlayingBlack:
14045       case AnalyzeMode:
14046       case AnalyzeFile:
14047         /* Skip if we know it isn't thinking */
14048         if (!cps->maybeThinking) return;
14049         if (appData.debugMode)
14050           fprintf(debugFP, "Interrupting %s\n", cps->which);
14051         InterruptChildProcess(cps->pr);
14052         cps->maybeThinking = FALSE;
14053         break;
14054       default:
14055         break;
14056     }
14057 #endif /*ATTENTION*/
14058 }
14059
14060 int
14061 CheckFlags()
14062 {
14063     if (whiteTimeRemaining <= 0) {
14064         if (!whiteFlag) {
14065             whiteFlag = TRUE;
14066             if (appData.icsActive) {
14067                 if (appData.autoCallFlag &&
14068                     gameMode == IcsPlayingBlack && !blackFlag) {
14069                   SendToICS(ics_prefix);
14070                   SendToICS("flag\n");
14071                 }
14072             } else {
14073                 if (blackFlag) {
14074                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14075                 } else {
14076                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14077                     if (appData.autoCallFlag) {
14078                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14079                         return TRUE;
14080                     }
14081                 }
14082             }
14083         }
14084     }
14085     if (blackTimeRemaining <= 0) {
14086         if (!blackFlag) {
14087             blackFlag = TRUE;
14088             if (appData.icsActive) {
14089                 if (appData.autoCallFlag &&
14090                     gameMode == IcsPlayingWhite && !whiteFlag) {
14091                   SendToICS(ics_prefix);
14092                   SendToICS("flag\n");
14093                 }
14094             } else {
14095                 if (whiteFlag) {
14096                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14097                 } else {
14098                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14099                     if (appData.autoCallFlag) {
14100                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14101                         return TRUE;
14102                     }
14103                 }
14104             }
14105         }
14106     }
14107     return FALSE;
14108 }
14109
14110 void
14111 CheckTimeControl()
14112 {
14113     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14114         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14115
14116     /*
14117      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14118      */
14119     if ( !WhiteOnMove(forwardMostMove) )
14120         /* White made time control */
14121         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14122         /* [HGM] time odds: correct new time quota for time odds! */
14123                                             / WhitePlayer()->timeOdds;
14124       else
14125         /* Black made time control */
14126         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14127                                             / WhitePlayer()->other->timeOdds;
14128 }
14129
14130 void
14131 DisplayBothClocks()
14132 {
14133     int wom = gameMode == EditPosition ?
14134       !blackPlaysFirst : WhiteOnMove(currentMove);
14135     DisplayWhiteClock(whiteTimeRemaining, wom);
14136     DisplayBlackClock(blackTimeRemaining, !wom);
14137 }
14138
14139
14140 /* Timekeeping seems to be a portability nightmare.  I think everyone
14141    has ftime(), but I'm really not sure, so I'm including some ifdefs
14142    to use other calls if you don't.  Clocks will be less accurate if
14143    you have neither ftime nor gettimeofday.
14144 */
14145
14146 /* VS 2008 requires the #include outside of the function */
14147 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14148 #include <sys/timeb.h>
14149 #endif
14150
14151 /* Get the current time as a TimeMark */
14152 void
14153 GetTimeMark(tm)
14154      TimeMark *tm;
14155 {
14156 #if HAVE_GETTIMEOFDAY
14157
14158     struct timeval timeVal;
14159     struct timezone timeZone;
14160
14161     gettimeofday(&timeVal, &timeZone);
14162     tm->sec = (long) timeVal.tv_sec; 
14163     tm->ms = (int) (timeVal.tv_usec / 1000L);
14164
14165 #else /*!HAVE_GETTIMEOFDAY*/
14166 #if HAVE_FTIME
14167
14168 // include <sys/timeb.h> / moved to just above start of function
14169     struct timeb timeB;
14170
14171     ftime(&timeB);
14172     tm->sec = (long) timeB.time;
14173     tm->ms = (int) timeB.millitm;
14174
14175 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14176     tm->sec = (long) time(NULL);
14177     tm->ms = 0;
14178 #endif
14179 #endif
14180 }
14181
14182 /* Return the difference in milliseconds between two
14183    time marks.  We assume the difference will fit in a long!
14184 */
14185 long
14186 SubtractTimeMarks(tm2, tm1)
14187      TimeMark *tm2, *tm1;
14188 {
14189     return 1000L*(tm2->sec - tm1->sec) +
14190            (long) (tm2->ms - tm1->ms);
14191 }
14192
14193
14194 /*
14195  * Code to manage the game clocks.
14196  *
14197  * In tournament play, black starts the clock and then white makes a move.
14198  * We give the human user a slight advantage if he is playing white---the
14199  * clocks don't run until he makes his first move, so it takes zero time.
14200  * Also, we don't account for network lag, so we could get out of sync
14201  * with GNU Chess's clock -- but then, referees are always right.  
14202  */
14203
14204 static TimeMark tickStartTM;
14205 static long intendedTickLength;
14206
14207 long
14208 NextTickLength(timeRemaining)
14209      long timeRemaining;
14210 {
14211     long nominalTickLength, nextTickLength;
14212
14213     if (timeRemaining > 0L && timeRemaining <= 10000L)
14214       nominalTickLength = 100L;
14215     else
14216       nominalTickLength = 1000L;
14217     nextTickLength = timeRemaining % nominalTickLength;
14218     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14219
14220     return nextTickLength;
14221 }
14222
14223 /* Adjust clock one minute up or down */
14224 void
14225 AdjustClock(Boolean which, int dir)
14226 {
14227     if(which) blackTimeRemaining += 60000*dir;
14228     else      whiteTimeRemaining += 60000*dir;
14229     DisplayBothClocks();
14230 }
14231
14232 /* Stop clocks and reset to a fresh time control */
14233 void
14234 ResetClocks() 
14235 {
14236     (void) StopClockTimer();
14237     if (appData.icsActive) {
14238         whiteTimeRemaining = blackTimeRemaining = 0;
14239     } else if (searchTime) {
14240         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14241         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14242     } else { /* [HGM] correct new time quote for time odds */
14243         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14244         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14245     }
14246     if (whiteFlag || blackFlag) {
14247         DisplayTitle("");
14248         whiteFlag = blackFlag = FALSE;
14249     }
14250     DisplayBothClocks();
14251 }
14252
14253 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14254
14255 /* Decrement running clock by amount of time that has passed */
14256 void
14257 DecrementClocks()
14258 {
14259     long timeRemaining;
14260     long lastTickLength, fudge;
14261     TimeMark now;
14262
14263     if (!appData.clockMode) return;
14264     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14265         
14266     GetTimeMark(&now);
14267
14268     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14269
14270     /* Fudge if we woke up a little too soon */
14271     fudge = intendedTickLength - lastTickLength;
14272     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14273
14274     if (WhiteOnMove(forwardMostMove)) {
14275         if(whiteNPS >= 0) lastTickLength = 0;
14276         timeRemaining = whiteTimeRemaining -= lastTickLength;
14277         DisplayWhiteClock(whiteTimeRemaining - fudge,
14278                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14279     } else {
14280         if(blackNPS >= 0) lastTickLength = 0;
14281         timeRemaining = blackTimeRemaining -= lastTickLength;
14282         DisplayBlackClock(blackTimeRemaining - fudge,
14283                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14284     }
14285
14286     if (CheckFlags()) return;
14287         
14288     tickStartTM = now;
14289     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14290     StartClockTimer(intendedTickLength);
14291
14292     /* if the time remaining has fallen below the alarm threshold, sound the
14293      * alarm. if the alarm has sounded and (due to a takeback or time control
14294      * with increment) the time remaining has increased to a level above the
14295      * threshold, reset the alarm so it can sound again. 
14296      */
14297     
14298     if (appData.icsActive && appData.icsAlarm) {
14299
14300         /* make sure we are dealing with the user's clock */
14301         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14302                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14303            )) return;
14304
14305         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14306             alarmSounded = FALSE;
14307         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
14308             PlayAlarmSound();
14309             alarmSounded = TRUE;
14310         }
14311     }
14312 }
14313
14314
14315 /* A player has just moved, so stop the previously running
14316    clock and (if in clock mode) start the other one.
14317    We redisplay both clocks in case we're in ICS mode, because
14318    ICS gives us an update to both clocks after every move.
14319    Note that this routine is called *after* forwardMostMove
14320    is updated, so the last fractional tick must be subtracted
14321    from the color that is *not* on move now.
14322 */
14323 void
14324 SwitchClocks(int newMoveNr)
14325 {
14326     long lastTickLength;
14327     TimeMark now;
14328     int flagged = FALSE;
14329
14330     GetTimeMark(&now);
14331
14332     if (StopClockTimer() && appData.clockMode) {
14333         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14334         if (!WhiteOnMove(forwardMostMove)) {
14335             if(blackNPS >= 0) lastTickLength = 0;
14336             blackTimeRemaining -= lastTickLength;
14337            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14338 //         if(pvInfoList[forwardMostMove-1].time == -1)
14339                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14340                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14341         } else {
14342            if(whiteNPS >= 0) lastTickLength = 0;
14343            whiteTimeRemaining -= lastTickLength;
14344            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14345 //         if(pvInfoList[forwardMostMove-1].time == -1)
14346                  pvInfoList[forwardMostMove-1].time = 
14347                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14348         }
14349         flagged = CheckFlags();
14350     }
14351     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14352     CheckTimeControl();
14353
14354     if (flagged || !appData.clockMode) return;
14355
14356     switch (gameMode) {
14357       case MachinePlaysBlack:
14358       case MachinePlaysWhite:
14359       case BeginningOfGame:
14360         if (pausing) return;
14361         break;
14362
14363       case EditGame:
14364       case PlayFromGameFile:
14365       case IcsExamining:
14366         return;
14367
14368       default:
14369         break;
14370     }
14371
14372     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14373         if(WhiteOnMove(forwardMostMove))
14374              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14375         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14376     }
14377
14378     tickStartTM = now;
14379     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14380       whiteTimeRemaining : blackTimeRemaining);
14381     StartClockTimer(intendedTickLength);
14382 }
14383         
14384
14385 /* Stop both clocks */
14386 void
14387 StopClocks()
14388 {       
14389     long lastTickLength;
14390     TimeMark now;
14391
14392     if (!StopClockTimer()) return;
14393     if (!appData.clockMode) return;
14394
14395     GetTimeMark(&now);
14396
14397     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14398     if (WhiteOnMove(forwardMostMove)) {
14399         if(whiteNPS >= 0) lastTickLength = 0;
14400         whiteTimeRemaining -= lastTickLength;
14401         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14402     } else {
14403         if(blackNPS >= 0) lastTickLength = 0;
14404         blackTimeRemaining -= lastTickLength;
14405         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14406     }
14407     CheckFlags();
14408 }
14409         
14410 /* Start clock of player on move.  Time may have been reset, so
14411    if clock is already running, stop and restart it. */
14412 void
14413 StartClocks()
14414 {
14415     (void) StopClockTimer(); /* in case it was running already */
14416     DisplayBothClocks();
14417     if (CheckFlags()) return;
14418
14419     if (!appData.clockMode) return;
14420     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14421
14422     GetTimeMark(&tickStartTM);
14423     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14424       whiteTimeRemaining : blackTimeRemaining);
14425
14426    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14427     whiteNPS = blackNPS = -1; 
14428     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14429        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14430         whiteNPS = first.nps;
14431     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14432        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14433         blackNPS = first.nps;
14434     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14435         whiteNPS = second.nps;
14436     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14437         blackNPS = second.nps;
14438     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14439
14440     StartClockTimer(intendedTickLength);
14441 }
14442
14443 char *
14444 TimeString(ms)
14445      long ms;
14446 {
14447     long second, minute, hour, day;
14448     char *sign = "";
14449     static char buf[32];
14450     
14451     if (ms > 0 && ms <= 9900) {
14452       /* convert milliseconds to tenths, rounding up */
14453       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14454
14455       sprintf(buf, " %03.1f ", tenths/10.0);
14456       return buf;
14457     }
14458
14459     /* convert milliseconds to seconds, rounding up */
14460     /* use floating point to avoid strangeness of integer division
14461        with negative dividends on many machines */
14462     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14463
14464     if (second < 0) {
14465         sign = "-";
14466         second = -second;
14467     }
14468     
14469     day = second / (60 * 60 * 24);
14470     second = second % (60 * 60 * 24);
14471     hour = second / (60 * 60);
14472     second = second % (60 * 60);
14473     minute = second / 60;
14474     second = second % 60;
14475     
14476     if (day > 0)
14477       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14478               sign, day, hour, minute, second);
14479     else if (hour > 0)
14480       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14481     else
14482       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14483     
14484     return buf;
14485 }
14486
14487
14488 /*
14489  * This is necessary because some C libraries aren't ANSI C compliant yet.
14490  */
14491 char *
14492 StrStr(string, match)
14493      char *string, *match;
14494 {
14495     int i, length;
14496     
14497     length = strlen(match);
14498     
14499     for (i = strlen(string) - length; i >= 0; i--, string++)
14500       if (!strncmp(match, string, length))
14501         return string;
14502     
14503     return NULL;
14504 }
14505
14506 char *
14507 StrCaseStr(string, match)
14508      char *string, *match;
14509 {
14510     int i, j, length;
14511     
14512     length = strlen(match);
14513     
14514     for (i = strlen(string) - length; i >= 0; i--, string++) {
14515         for (j = 0; j < length; j++) {
14516             if (ToLower(match[j]) != ToLower(string[j]))
14517               break;
14518         }
14519         if (j == length) return string;
14520     }
14521
14522     return NULL;
14523 }
14524
14525 #ifndef _amigados
14526 int
14527 StrCaseCmp(s1, s2)
14528      char *s1, *s2;
14529 {
14530     char c1, c2;
14531     
14532     for (;;) {
14533         c1 = ToLower(*s1++);
14534         c2 = ToLower(*s2++);
14535         if (c1 > c2) return 1;
14536         if (c1 < c2) return -1;
14537         if (c1 == NULLCHAR) return 0;
14538     }
14539 }
14540
14541
14542 int
14543 ToLower(c)
14544      int c;
14545 {
14546     return isupper(c) ? tolower(c) : c;
14547 }
14548
14549
14550 int
14551 ToUpper(c)
14552      int c;
14553 {
14554     return islower(c) ? toupper(c) : c;
14555 }
14556 #endif /* !_amigados    */
14557
14558 char *
14559 StrSave(s)
14560      char *s;
14561 {
14562     char *ret;
14563
14564     if ((ret = (char *) malloc(strlen(s) + 1))) {
14565         strcpy(ret, s);
14566     }
14567     return ret;
14568 }
14569
14570 char *
14571 StrSavePtr(s, savePtr)
14572      char *s, **savePtr;
14573 {
14574     if (*savePtr) {
14575         free(*savePtr);
14576     }
14577     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14578         strcpy(*savePtr, s);
14579     }
14580     return(*savePtr);
14581 }
14582
14583 char *
14584 PGNDate()
14585 {
14586     time_t clock;
14587     struct tm *tm;
14588     char buf[MSG_SIZ];
14589
14590     clock = time((time_t *)NULL);
14591     tm = localtime(&clock);
14592     sprintf(buf, "%04d.%02d.%02d",
14593             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14594     return StrSave(buf);
14595 }
14596
14597
14598 char *
14599 PositionToFEN(move, overrideCastling)
14600      int move;
14601      char *overrideCastling;
14602 {
14603     int i, j, fromX, fromY, toX, toY;
14604     int whiteToPlay;
14605     char buf[128];
14606     char *p, *q;
14607     int emptycount;
14608     ChessSquare piece;
14609
14610     whiteToPlay = (gameMode == EditPosition) ?
14611       !blackPlaysFirst : (move % 2 == 0);
14612     p = buf;
14613
14614     /* Piece placement data */
14615     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14616         emptycount = 0;
14617         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14618             if (boards[move][i][j] == EmptySquare) {
14619                 emptycount++;
14620             } else { ChessSquare piece = boards[move][i][j];
14621                 if (emptycount > 0) {
14622                     if(emptycount<10) /* [HGM] can be >= 10 */
14623                         *p++ = '0' + emptycount;
14624                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14625                     emptycount = 0;
14626                 }
14627                 if(PieceToChar(piece) == '+') {
14628                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14629                     *p++ = '+';
14630                     piece = (ChessSquare)(DEMOTED piece);
14631                 } 
14632                 *p++ = PieceToChar(piece);
14633                 if(p[-1] == '~') {
14634                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14635                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14636                     *p++ = '~';
14637                 }
14638             }
14639         }
14640         if (emptycount > 0) {
14641             if(emptycount<10) /* [HGM] can be >= 10 */
14642                 *p++ = '0' + emptycount;
14643             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14644             emptycount = 0;
14645         }
14646         *p++ = '/';
14647     }
14648     *(p - 1) = ' ';
14649
14650     /* [HGM] print Crazyhouse or Shogi holdings */
14651     if( gameInfo.holdingsWidth ) {
14652         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14653         q = p;
14654         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14655             piece = boards[move][i][BOARD_WIDTH-1];
14656             if( piece != EmptySquare )
14657               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14658                   *p++ = PieceToChar(piece);
14659         }
14660         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14661             piece = boards[move][BOARD_HEIGHT-i-1][0];
14662             if( piece != EmptySquare )
14663               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14664                   *p++ = PieceToChar(piece);
14665         }
14666
14667         if( q == p ) *p++ = '-';
14668         *p++ = ']';
14669         *p++ = ' ';
14670     }
14671
14672     /* Active color */
14673     *p++ = whiteToPlay ? 'w' : 'b';
14674     *p++ = ' ';
14675
14676   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14677     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14678   } else {
14679   if(nrCastlingRights) {
14680      q = p;
14681      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14682        /* [HGM] write directly from rights */
14683            if(boards[move][CASTLING][2] != NoRights &&
14684               boards[move][CASTLING][0] != NoRights   )
14685                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14686            if(boards[move][CASTLING][2] != NoRights &&
14687               boards[move][CASTLING][1] != NoRights   )
14688                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14689            if(boards[move][CASTLING][5] != NoRights &&
14690               boards[move][CASTLING][3] != NoRights   )
14691                 *p++ = boards[move][CASTLING][3] + AAA;
14692            if(boards[move][CASTLING][5] != NoRights &&
14693               boards[move][CASTLING][4] != NoRights   )
14694                 *p++ = boards[move][CASTLING][4] + AAA;
14695      } else {
14696
14697         /* [HGM] write true castling rights */
14698         if( nrCastlingRights == 6 ) {
14699             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14700                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14701             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14702                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14703             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14704                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14705             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14706                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14707         }
14708      }
14709      if (q == p) *p++ = '-'; /* No castling rights */
14710      *p++ = ' ';
14711   }
14712
14713   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14714      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14715     /* En passant target square */
14716     if (move > backwardMostMove) {
14717         fromX = moveList[move - 1][0] - AAA;
14718         fromY = moveList[move - 1][1] - ONE;
14719         toX = moveList[move - 1][2] - AAA;
14720         toY = moveList[move - 1][3] - ONE;
14721         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14722             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14723             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14724             fromX == toX) {
14725             /* 2-square pawn move just happened */
14726             *p++ = toX + AAA;
14727             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14728         } else {
14729             *p++ = '-';
14730         }
14731     } else if(move == backwardMostMove) {
14732         // [HGM] perhaps we should always do it like this, and forget the above?
14733         if((signed char)boards[move][EP_STATUS] >= 0) {
14734             *p++ = boards[move][EP_STATUS] + AAA;
14735             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14736         } else {
14737             *p++ = '-';
14738         }
14739     } else {
14740         *p++ = '-';
14741     }
14742     *p++ = ' ';
14743   }
14744   }
14745
14746     /* [HGM] find reversible plies */
14747     {   int i = 0, j=move;
14748
14749         if (appData.debugMode) { int k;
14750             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14751             for(k=backwardMostMove; k<=forwardMostMove; k++)
14752                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14753
14754         }
14755
14756         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14757         if( j == backwardMostMove ) i += initialRulePlies;
14758         sprintf(p, "%d ", i);
14759         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14760     }
14761     /* Fullmove number */
14762     sprintf(p, "%d", (move / 2) + 1);
14763     
14764     return StrSave(buf);
14765 }
14766
14767 Boolean
14768 ParseFEN(board, blackPlaysFirst, fen)
14769     Board board;
14770      int *blackPlaysFirst;
14771      char *fen;
14772 {
14773     int i, j;
14774     char *p, c;
14775     int emptycount;
14776     ChessSquare piece;
14777
14778     p = fen;
14779
14780     /* [HGM] by default clear Crazyhouse holdings, if present */
14781     if(gameInfo.holdingsWidth) {
14782        for(i=0; i<BOARD_HEIGHT; i++) {
14783            board[i][0]             = EmptySquare; /* black holdings */
14784            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14785            board[i][1]             = (ChessSquare) 0; /* black counts */
14786            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14787        }
14788     }
14789
14790     /* Piece placement data */
14791     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14792         j = 0;
14793         for (;;) {
14794             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14795                 if (*p == '/') p++;
14796                 emptycount = gameInfo.boardWidth - j;
14797                 while (emptycount--)
14798                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14799                 break;
14800 #if(BOARD_FILES >= 10)
14801             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14802                 p++; emptycount=10;
14803                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14804                 while (emptycount--)
14805                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14806 #endif
14807             } else if (isdigit(*p)) {
14808                 emptycount = *p++ - '0';
14809                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14810                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14811                 while (emptycount--)
14812                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14813             } else if (*p == '+' || isalpha(*p)) {
14814                 if (j >= gameInfo.boardWidth) return FALSE;
14815                 if(*p=='+') {
14816                     piece = CharToPiece(*++p);
14817                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14818                     piece = (ChessSquare) (PROMOTED piece ); p++;
14819                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14820                 } else piece = CharToPiece(*p++);
14821
14822                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14823                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14824                     piece = (ChessSquare) (PROMOTED piece);
14825                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14826                     p++;
14827                 }
14828                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14829             } else {
14830                 return FALSE;
14831             }
14832         }
14833     }
14834     while (*p == '/' || *p == ' ') p++;
14835
14836     /* [HGM] look for Crazyhouse holdings here */
14837     while(*p==' ') p++;
14838     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14839         if(*p == '[') p++;
14840         if(*p == '-' ) *p++; /* empty holdings */ else {
14841             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14842             /* if we would allow FEN reading to set board size, we would   */
14843             /* have to add holdings and shift the board read so far here   */
14844             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14845                 *p++;
14846                 if((int) piece >= (int) BlackPawn ) {
14847                     i = (int)piece - (int)BlackPawn;
14848                     i = PieceToNumber((ChessSquare)i);
14849                     if( i >= gameInfo.holdingsSize ) return FALSE;
14850                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14851                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14852                 } else {
14853                     i = (int)piece - (int)WhitePawn;
14854                     i = PieceToNumber((ChessSquare)i);
14855                     if( i >= gameInfo.holdingsSize ) return FALSE;
14856                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14857                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14858                 }
14859             }
14860         }
14861         if(*p == ']') *p++;
14862     }
14863
14864     while(*p == ' ') p++;
14865
14866     /* Active color */
14867     c = *p++;
14868     if(appData.colorNickNames) {
14869       if( c == appData.colorNickNames[0] ) c = 'w'; else
14870       if( c == appData.colorNickNames[1] ) c = 'b';
14871     }
14872     switch (c) {
14873       case 'w':
14874         *blackPlaysFirst = FALSE;
14875         break;
14876       case 'b': 
14877         *blackPlaysFirst = TRUE;
14878         break;
14879       default:
14880         return FALSE;
14881     }
14882
14883     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14884     /* return the extra info in global variiables             */
14885
14886     /* set defaults in case FEN is incomplete */
14887     board[EP_STATUS] = EP_UNKNOWN;
14888     for(i=0; i<nrCastlingRights; i++ ) {
14889         board[CASTLING][i] =
14890             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14891     }   /* assume possible unless obviously impossible */
14892     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14893     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14894     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14895                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14896     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14897     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14898     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14899                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14900     FENrulePlies = 0;
14901
14902     while(*p==' ') p++;
14903     if(nrCastlingRights) {
14904       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14905           /* castling indicator present, so default becomes no castlings */
14906           for(i=0; i<nrCastlingRights; i++ ) {
14907                  board[CASTLING][i] = NoRights;
14908           }
14909       }
14910       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14911              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14912              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14913              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14914         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14915
14916         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14917             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14918             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14919         }
14920         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14921             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14922         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14923                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14924         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14925                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14926         switch(c) {
14927           case'K':
14928               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14929               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14930               board[CASTLING][2] = whiteKingFile;
14931               break;
14932           case'Q':
14933               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14934               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14935               board[CASTLING][2] = whiteKingFile;
14936               break;
14937           case'k':
14938               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14939               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14940               board[CASTLING][5] = blackKingFile;
14941               break;
14942           case'q':
14943               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14944               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14945               board[CASTLING][5] = blackKingFile;
14946           case '-':
14947               break;
14948           default: /* FRC castlings */
14949               if(c >= 'a') { /* black rights */
14950                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14951                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14952                   if(i == BOARD_RGHT) break;
14953                   board[CASTLING][5] = i;
14954                   c -= AAA;
14955                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14956                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14957                   if(c > i)
14958                       board[CASTLING][3] = c;
14959                   else
14960                       board[CASTLING][4] = c;
14961               } else { /* white rights */
14962                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14963                     if(board[0][i] == WhiteKing) break;
14964                   if(i == BOARD_RGHT) break;
14965                   board[CASTLING][2] = i;
14966                   c -= AAA - 'a' + 'A';
14967                   if(board[0][c] >= WhiteKing) break;
14968                   if(c > i)
14969                       board[CASTLING][0] = c;
14970                   else
14971                       board[CASTLING][1] = c;
14972               }
14973         }
14974       }
14975       for(i=0; i<nrCastlingRights; i++)
14976         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14977     if (appData.debugMode) {
14978         fprintf(debugFP, "FEN castling rights:");
14979         for(i=0; i<nrCastlingRights; i++)
14980         fprintf(debugFP, " %d", board[CASTLING][i]);
14981         fprintf(debugFP, "\n");
14982     }
14983
14984       while(*p==' ') p++;
14985     }
14986
14987     /* read e.p. field in games that know e.p. capture */
14988     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14989        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14990       if(*p=='-') {
14991         p++; board[EP_STATUS] = EP_NONE;
14992       } else {
14993          char c = *p++ - AAA;
14994
14995          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14996          if(*p >= '0' && *p <='9') *p++;
14997          board[EP_STATUS] = c;
14998       }
14999     }
15000
15001
15002     if(sscanf(p, "%d", &i) == 1) {
15003         FENrulePlies = i; /* 50-move ply counter */
15004         /* (The move number is still ignored)    */
15005     }
15006
15007     return TRUE;
15008 }
15009       
15010 void
15011 EditPositionPasteFEN(char *fen)
15012 {
15013   if (fen != NULL) {
15014     Board initial_position;
15015
15016     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15017       DisplayError(_("Bad FEN position in clipboard"), 0);
15018       return ;
15019     } else {
15020       int savedBlackPlaysFirst = blackPlaysFirst;
15021       EditPositionEvent();
15022       blackPlaysFirst = savedBlackPlaysFirst;
15023       CopyBoard(boards[0], initial_position);
15024       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15025       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15026       DisplayBothClocks();
15027       DrawPosition(FALSE, boards[currentMove]);
15028     }
15029   }
15030 }
15031
15032 static char cseq[12] = "\\   ";
15033
15034 Boolean set_cont_sequence(char *new_seq)
15035 {
15036     int len;
15037     Boolean ret;
15038
15039     // handle bad attempts to set the sequence
15040         if (!new_seq)
15041                 return 0; // acceptable error - no debug
15042
15043     len = strlen(new_seq);
15044     ret = (len > 0) && (len < sizeof(cseq));
15045     if (ret)
15046         strcpy(cseq, new_seq);
15047     else if (appData.debugMode)
15048         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15049     return ret;
15050 }
15051
15052 /*
15053     reformat a source message so words don't cross the width boundary.  internal
15054     newlines are not removed.  returns the wrapped size (no null character unless
15055     included in source message).  If dest is NULL, only calculate the size required
15056     for the dest buffer.  lp argument indicats line position upon entry, and it's
15057     passed back upon exit.
15058 */
15059 int wrap(char *dest, char *src, int count, int width, int *lp)
15060 {
15061     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15062
15063     cseq_len = strlen(cseq);
15064     old_line = line = *lp;
15065     ansi = len = clen = 0;
15066
15067     for (i=0; i < count; i++)
15068     {
15069         if (src[i] == '\033')
15070             ansi = 1;
15071
15072         // if we hit the width, back up
15073         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15074         {
15075             // store i & len in case the word is too long
15076             old_i = i, old_len = len;
15077
15078             // find the end of the last word
15079             while (i && src[i] != ' ' && src[i] != '\n')
15080             {
15081                 i--;
15082                 len--;
15083             }
15084
15085             // word too long?  restore i & len before splitting it
15086             if ((old_i-i+clen) >= width)
15087             {
15088                 i = old_i;
15089                 len = old_len;
15090             }
15091
15092             // extra space?
15093             if (i && src[i-1] == ' ')
15094                 len--;
15095
15096             if (src[i] != ' ' && src[i] != '\n')
15097             {
15098                 i--;
15099                 if (len)
15100                     len--;
15101             }
15102
15103             // now append the newline and continuation sequence
15104             if (dest)
15105                 dest[len] = '\n';
15106             len++;
15107             if (dest)
15108                 strncpy(dest+len, cseq, cseq_len);
15109             len += cseq_len;
15110             line = cseq_len;
15111             clen = cseq_len;
15112             continue;
15113         }
15114
15115         if (dest)
15116             dest[len] = src[i];
15117         len++;
15118         if (!ansi)
15119             line++;
15120         if (src[i] == '\n')
15121             line = 0;
15122         if (src[i] == 'm')
15123             ansi = 0;
15124     }
15125     if (dest && appData.debugMode)
15126     {
15127         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15128             count, width, line, len, *lp);
15129         show_bytes(debugFP, src, count);
15130         fprintf(debugFP, "\ndest: ");
15131         show_bytes(debugFP, dest, len);
15132         fprintf(debugFP, "\n");
15133     }
15134     *lp = dest ? line : old_line;
15135
15136     return len;
15137 }
15138
15139 // [HGM] vari: routines for shelving variations
15140
15141 void 
15142 PushTail(int firstMove, int lastMove)
15143 {
15144         int i, j, nrMoves = lastMove - firstMove;
15145
15146         if(appData.icsActive) { // only in local mode
15147                 forwardMostMove = currentMove; // mimic old ICS behavior
15148                 return;
15149         }
15150         if(storedGames >= MAX_VARIATIONS-1) return;
15151
15152         // push current tail of game on stack
15153         savedResult[storedGames] = gameInfo.result;
15154         savedDetails[storedGames] = gameInfo.resultDetails;
15155         gameInfo.resultDetails = NULL;
15156         savedFirst[storedGames] = firstMove;
15157         savedLast [storedGames] = lastMove;
15158         savedFramePtr[storedGames] = framePtr;
15159         framePtr -= nrMoves; // reserve space for the boards
15160         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15161             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15162             for(j=0; j<MOVE_LEN; j++)
15163                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15164             for(j=0; j<2*MOVE_LEN; j++)
15165                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15166             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15167             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15168             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15169             pvInfoList[firstMove+i-1].depth = 0;
15170             commentList[framePtr+i] = commentList[firstMove+i];
15171             commentList[firstMove+i] = NULL;
15172         }
15173
15174         storedGames++;
15175         forwardMostMove = firstMove; // truncate game so we can start variation
15176         if(storedGames == 1) GreyRevert(FALSE);
15177 }
15178
15179 Boolean
15180 PopTail(Boolean annotate)
15181 {
15182         int i, j, nrMoves;
15183         char buf[8000], moveBuf[20];
15184
15185         if(appData.icsActive) return FALSE; // only in local mode
15186         if(!storedGames) return FALSE; // sanity
15187         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15188
15189         storedGames--;
15190         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15191         nrMoves = savedLast[storedGames] - currentMove;
15192         if(annotate) {
15193                 int cnt = 10;
15194                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15195                 else strcpy(buf, "(");
15196                 for(i=currentMove; i<forwardMostMove; i++) {
15197                         if(WhiteOnMove(i))
15198                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15199                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15200                         strcat(buf, moveBuf);
15201                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15202                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15203                 }
15204                 strcat(buf, ")");
15205         }
15206         for(i=1; i<=nrMoves; i++) { // copy last variation back
15207             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15208             for(j=0; j<MOVE_LEN; j++)
15209                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15210             for(j=0; j<2*MOVE_LEN; j++)
15211                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15212             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15213             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15214             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15215             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15216             commentList[currentMove+i] = commentList[framePtr+i];
15217             commentList[framePtr+i] = NULL;
15218         }
15219         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15220         framePtr = savedFramePtr[storedGames];
15221         gameInfo.result = savedResult[storedGames];
15222         if(gameInfo.resultDetails != NULL) {
15223             free(gameInfo.resultDetails);
15224       }
15225         gameInfo.resultDetails = savedDetails[storedGames];
15226         forwardMostMove = currentMove + nrMoves;
15227         if(storedGames == 0) GreyRevert(TRUE);
15228         return TRUE;
15229 }
15230
15231 void 
15232 CleanupTail()
15233 {       // remove all shelved variations
15234         int i;
15235         for(i=0; i<storedGames; i++) {
15236             if(savedDetails[i])
15237                 free(savedDetails[i]);
15238             savedDetails[i] = NULL;
15239         }
15240         for(i=framePtr; i<MAX_MOVES; i++) {
15241                 if(commentList[i]) free(commentList[i]);
15242                 commentList[i] = NULL;
15243         }
15244         framePtr = MAX_MOVES-1;
15245         storedGames = 0;
15246 }
15247
15248 void
15249 LoadVariation(int index, char *text)
15250 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15251         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15252         int level = 0, move;
15253
15254         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15255         // first find outermost bracketing variation
15256         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15257             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15258                 if(*p == '{') wait = '}'; else
15259                 if(*p == '[') wait = ']'; else
15260                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15261                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15262             }
15263             if(*p == wait) wait = NULLCHAR; // closing ]} found
15264             p++;
15265         }
15266         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15267         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15268         end[1] = NULLCHAR; // clip off comment beyond variation
15269         ToNrEvent(currentMove-1);
15270         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15271         // kludge: use ParsePV() to append variation to game
15272         move = currentMove;
15273         ParsePV(start, TRUE);
15274         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15275         ClearPremoveHighlights();
15276         CommentPopDown();
15277         ToNrEvent(currentMove+1);
15278 }
15279