Fix regression in colorization of zippy-matched commands
[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 #else 
135 # define _(s) (s) 
136 # define N_(s) s 
137 #endif 
138
139
140 /* A point in time */
141 typedef struct {
142     long sec;  /* Assuming this is >= 32 bits */
143     int ms;    /* Assuming this is >= 16 bits */
144 } TimeMark;
145
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148                          char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150                       char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
155                       int toX, int toY));
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163                                                                                 Board board));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 int Adjudicate P((ChessProgramState *cps));
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
172 void EditPositionDone P((Boolean fakeRights));
173 void PrintOpponents P((FILE *fp));
174 void PrintPosition P((FILE *fp, int move));
175 void StartChessProgram P((ChessProgramState *cps));
176 void SendToProgram P((char *message, ChessProgramState *cps));
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
179                            char *buf, int count, int error));
180 void SendTimeControl P((ChessProgramState *cps,
181                         int mps, long tc, int inc, int sd, int st));
182 char *TimeControlTagValue P((void));
183 void Attention P((ChessProgramState *cps));
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
185 void ResurrectChessProgram P((void));
186 void DisplayComment P((int moveNumber, char *text));
187 void DisplayMove P((int moveNumber));
188
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void KeepAlive P((void));
192 void StartClocks P((void));
193 void SwitchClocks P((int nr));
194 void StopClocks P((void));
195 void ResetClocks P((void));
196 char *PGNDate P((void));
197 void SetGameInfo P((void));
198 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 void GetTimeMark P((TimeMark *));
208 long SubtractTimeMarks P((TimeMark *, TimeMark *));
209 int CheckFlags P((void));
210 long NextTickLength P((long));
211 void CheckTimeControl P((void));
212 void show_bytes P((FILE *, char *, int));
213 int string_to_rating P((char *str));
214 void ParseFeatures P((char* args, ChessProgramState *cps));
215 void InitBackEnd3 P((void));
216 void FeatureDone P((ChessProgramState* cps, int val));
217 void InitChessProgram P((ChessProgramState *cps, int setup));
218 void OutputKibitz(int window, char *text);
219 int PerpetualChase(int first, int last);
220 int EngineOutputIsUp();
221 void InitDrawingSizes(int x, int y);
222
223 #ifdef WIN32
224        extern void ConsoleCreate();
225 #endif
226
227 ChessProgramState *WhitePlayer();
228 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
229 int VerifyDisplayMode P(());
230
231 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
232 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
233 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
234 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
235 void ics_update_width P((int new_width));
236 extern char installDir[MSG_SIZ];
237 VariantClass startVariant; /* [HGM] nicks: initial variant */
238
239 extern int tinyLayout, smallLayout;
240 ChessProgramStats programStats;
241 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
242 int endPV = -1;
243 static int exiting = 0; /* [HGM] moved to top */
244 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
245 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
246 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
247 int partnerHighlight[2];
248 Boolean partnerBoardValid = 0;
249 char partnerStatus[MSG_SIZ];
250 Boolean partnerUp;
251 Boolean originalFlip;
252 Boolean twoBoards = 0;
253 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
254 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
255 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
256 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
257 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
258 int opponentKibitzes;
259 int lastSavedGame; /* [HGM] save: ID of game */
260 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
261 extern int chatCount;
262 int chattingPartner;
263 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
264
265 /* States for ics_getting_history */
266 #define H_FALSE 0
267 #define H_REQUESTED 1
268 #define H_GOT_REQ_HEADER 2
269 #define H_GOT_UNREQ_HEADER 3
270 #define H_GETTING_MOVES 4
271 #define H_GOT_UNWANTED_HEADER 5
272
273 /* whosays values for GameEnds */
274 #define GE_ICS 0
275 #define GE_ENGINE 1
276 #define GE_PLAYER 2
277 #define GE_FILE 3
278 #define GE_XBOARD 4
279 #define GE_ENGINE1 5
280 #define GE_ENGINE2 6
281
282 /* Maximum number of games in a cmail message */
283 #define CMAIL_MAX_GAMES 20
284
285 /* Different types of move when calling RegisterMove */
286 #define CMAIL_MOVE   0
287 #define CMAIL_RESIGN 1
288 #define CMAIL_DRAW   2
289 #define CMAIL_ACCEPT 3
290
291 /* Different types of result to remember for each game */
292 #define CMAIL_NOT_RESULT 0
293 #define CMAIL_OLD_RESULT 1
294 #define CMAIL_NEW_RESULT 2
295
296 /* Telnet protocol constants */
297 #define TN_WILL 0373
298 #define TN_WONT 0374
299 #define TN_DO   0375
300 #define TN_DONT 0376
301 #define TN_IAC  0377
302 #define TN_ECHO 0001
303 #define TN_SGA  0003
304 #define TN_PORT 23
305
306 /* [AS] */
307 static char * safeStrCpy( char * dst, const char * src, size_t count )
308 {
309     assert( dst != NULL );
310     assert( src != NULL );
311     assert( count > 0 );
312
313     strncpy( dst, src, count );
314     dst[ count-1 ] = '\0';
315     return dst;
316 }
317
318 /* Some compiler can't cast u64 to double
319  * This function do the job for us:
320
321  * We use the highest bit for cast, this only
322  * works if the highest bit is not
323  * in use (This should not happen)
324  *
325  * We used this for all compiler
326  */
327 double
328 u64ToDouble(u64 value)
329 {
330   double r;
331   u64 tmp = value & u64Const(0x7fffffffffffffff);
332   r = (double)(s64)tmp;
333   if (value & u64Const(0x8000000000000000))
334        r +=  9.2233720368547758080e18; /* 2^63 */
335  return r;
336 }
337
338 /* Fake up flags for now, as we aren't keeping track of castling
339    availability yet. [HGM] Change of logic: the flag now only
340    indicates the type of castlings allowed by the rule of the game.
341    The actual rights themselves are maintained in the array
342    castlingRights, as part of the game history, and are not probed
343    by this function.
344  */
345 int
346 PosFlags(index)
347 {
348   int flags = F_ALL_CASTLE_OK;
349   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
350   switch (gameInfo.variant) {
351   case VariantSuicide:
352     flags &= ~F_ALL_CASTLE_OK;
353   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
354     flags |= F_IGNORE_CHECK;
355   case VariantLosers:
356     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
357     break;
358   case VariantAtomic:
359     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
360     break;
361   case VariantKriegspiel:
362     flags |= F_KRIEGSPIEL_CAPTURE;
363     break;
364   case VariantCapaRandom: 
365   case VariantFischeRandom:
366     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
367   case VariantNoCastle:
368   case VariantShatranj:
369   case VariantCourier:
370   case VariantMakruk:
371     flags &= ~F_ALL_CASTLE_OK;
372     break;
373   default:
374     break;
375   }
376   return flags;
377 }
378
379 FILE *gameFileFP, *debugFP;
380
381 /* 
382     [AS] Note: sometimes, the sscanf() function is used to parse the input
383     into a fixed-size buffer. Because of this, we must be prepared to
384     receive strings as long as the size of the input buffer, which is currently
385     set to 4K for Windows and 8K for the rest.
386     So, we must either allocate sufficiently large buffers here, or
387     reduce the size of the input buffer in the input reading part.
388 */
389
390 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
391 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
392 char thinkOutput1[MSG_SIZ*10];
393
394 ChessProgramState first, second;
395
396 /* premove variables */
397 int premoveToX = 0;
398 int premoveToY = 0;
399 int premoveFromX = 0;
400 int premoveFromY = 0;
401 int premovePromoChar = 0;
402 int gotPremove = 0;
403 Boolean alarmSounded;
404 /* end premove variables */
405
406 char *ics_prefix = "$";
407 int ics_type = ICS_GENERIC;
408
409 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
410 int pauseExamForwardMostMove = 0;
411 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
412 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
413 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
414 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
415 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
416 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
417 int whiteFlag = FALSE, blackFlag = FALSE;
418 int userOfferedDraw = FALSE;
419 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
420 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
421 int cmailMoveType[CMAIL_MAX_GAMES];
422 long ics_clock_paused = 0;
423 ProcRef icsPR = NoProc, cmailPR = NoProc;
424 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
425 GameMode gameMode = BeginningOfGame;
426 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
427 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
428 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
429 int hiddenThinkOutputState = 0; /* [AS] */
430 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
431 int adjudicateLossPlies = 6;
432 char white_holding[64], black_holding[64];
433 TimeMark lastNodeCountTime;
434 long lastNodeCount=0;
435 int have_sent_ICS_logon = 0;
436 int movesPerSession;
437 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
438 long timeControl_2; /* [AS] Allow separate time controls */
439 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
440 long timeRemaining[2][MAX_MOVES];
441 int matchGame = 0;
442 TimeMark programStartTime;
443 char ics_handle[MSG_SIZ];
444 int have_set_title = 0;
445
446 /* animateTraining preserves the state of appData.animate
447  * when Training mode is activated. This allows the
448  * response to be animated when appData.animate == TRUE and
449  * appData.animateDragging == TRUE.
450  */
451 Boolean animateTraining;
452
453 GameInfo gameInfo;
454
455 AppData appData;
456
457 Board boards[MAX_MOVES];
458 /* [HGM] Following 7 needed for accurate legality tests: */
459 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
460 signed char  initialRights[BOARD_FILES];
461 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
462 int   initialRulePlies, FENrulePlies;
463 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
464 int loadFlag = 0; 
465 int shuffleOpenings;
466 int mute; // mute all sounds
467
468 // [HGM] vari: next 12 to save and restore variations
469 #define MAX_VARIATIONS 10
470 int framePtr = MAX_MOVES-1; // points to free stack entry
471 int storedGames = 0;
472 int savedFirst[MAX_VARIATIONS];
473 int savedLast[MAX_VARIATIONS];
474 int savedFramePtr[MAX_VARIATIONS];
475 char *savedDetails[MAX_VARIATIONS];
476 ChessMove savedResult[MAX_VARIATIONS];
477
478 void PushTail P((int firstMove, int lastMove));
479 Boolean PopTail P((Boolean annotate));
480 void CleanupTail P((void));
481
482 ChessSquare  FIDEArray[2][BOARD_FILES] = {
483     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
484         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
485     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
486         BlackKing, BlackBishop, BlackKnight, BlackRook }
487 };
488
489 ChessSquare twoKingsArray[2][BOARD_FILES] = {
490     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
491         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
492     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
493         BlackKing, BlackKing, BlackKnight, BlackRook }
494 };
495
496 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
497     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
498         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
499     { BlackRook, BlackMan, BlackBishop, BlackQueen,
500         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
501 };
502
503 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
504     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
505         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
506     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
507         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
508 };
509
510 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
511     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
512         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
513     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
514         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
515 };
516
517 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
518     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
519         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
520     { BlackRook, BlackKnight, BlackMan, BlackFerz,
521         BlackKing, BlackMan, BlackKnight, BlackRook }
522 };
523
524
525 #if (BOARD_FILES>=10)
526 ChessSquare ShogiArray[2][BOARD_FILES] = {
527     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
528         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
529     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
530         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
531 };
532
533 ChessSquare XiangqiArray[2][BOARD_FILES] = {
534     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
535         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
536     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
537         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
538 };
539
540 ChessSquare CapablancaArray[2][BOARD_FILES] = {
541     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
542         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
544         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
545 };
546
547 ChessSquare GreatArray[2][BOARD_FILES] = {
548     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
549         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
550     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
551         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
552 };
553
554 ChessSquare JanusArray[2][BOARD_FILES] = {
555     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
556         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
557     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
558         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
559 };
560
561 #ifdef GOTHIC
562 ChessSquare GothicArray[2][BOARD_FILES] = {
563     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
564         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
565     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
566         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
567 };
568 #else // !GOTHIC
569 #define GothicArray CapablancaArray
570 #endif // !GOTHIC
571
572 #ifdef FALCON
573 ChessSquare FalconArray[2][BOARD_FILES] = {
574     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
575         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
576     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
577         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
578 };
579 #else // !FALCON
580 #define FalconArray CapablancaArray
581 #endif // !FALCON
582
583 #else // !(BOARD_FILES>=10)
584 #define XiangqiPosition FIDEArray
585 #define CapablancaArray FIDEArray
586 #define GothicArray FIDEArray
587 #define GreatArray FIDEArray
588 #endif // !(BOARD_FILES>=10)
589
590 #if (BOARD_FILES>=12)
591 ChessSquare CourierArray[2][BOARD_FILES] = {
592     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
593         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
594     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
595         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
596 };
597 #else // !(BOARD_FILES>=12)
598 #define CourierArray CapablancaArray
599 #endif // !(BOARD_FILES>=12)
600
601
602 Board initialPosition;
603
604
605 /* Convert str to a rating. Checks for special cases of "----",
606
607    "++++", etc. Also strips ()'s */
608 int
609 string_to_rating(str)
610   char *str;
611 {
612   while(*str && !isdigit(*str)) ++str;
613   if (!*str)
614     return 0;   /* One of the special "no rating" cases */
615   else
616     return atoi(str);
617 }
618
619 void
620 ClearProgramStats()
621 {
622     /* Init programStats */
623     programStats.movelist[0] = 0;
624     programStats.depth = 0;
625     programStats.nr_moves = 0;
626     programStats.moves_left = 0;
627     programStats.nodes = 0;
628     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
629     programStats.score = 0;
630     programStats.got_only_move = 0;
631     programStats.got_fail = 0;
632     programStats.line_is_book = 0;
633 }
634
635 void
636 InitBackEnd1()
637 {
638     int matched, min, sec;
639
640     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
641     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
642
643     GetTimeMark(&programStartTime);
644     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
645
646     ClearProgramStats();
647     programStats.ok_to_send = 1;
648     programStats.seen_stat = 0;
649
650     /*
651      * Initialize game list
652      */
653     ListNew(&gameList);
654
655
656     /*
657      * Internet chess server status
658      */
659     if (appData.icsActive) {
660         appData.matchMode = FALSE;
661         appData.matchGames = 0;
662 #if ZIPPY       
663         appData.noChessProgram = !appData.zippyPlay;
664 #else
665         appData.zippyPlay = FALSE;
666         appData.zippyTalk = FALSE;
667         appData.noChessProgram = TRUE;
668 #endif
669         if (*appData.icsHelper != NULLCHAR) {
670             appData.useTelnet = TRUE;
671             appData.telnetProgram = appData.icsHelper;
672         }
673     } else {
674         appData.zippyTalk = appData.zippyPlay = FALSE;
675     }
676
677     /* [AS] Initialize pv info list [HGM] and game state */
678     {
679         int i, j;
680
681         for( i=0; i<=framePtr; i++ ) {
682             pvInfoList[i].depth = -1;
683             boards[i][EP_STATUS] = EP_NONE;
684             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
685         }
686     }
687
688     /*
689      * Parse timeControl resource
690      */
691     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
692                           appData.movesPerSession)) {
693         char buf[MSG_SIZ];
694         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
695         DisplayFatalError(buf, 0, 2);
696     }
697
698     /*
699      * Parse searchTime resource
700      */
701     if (*appData.searchTime != NULLCHAR) {
702         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
703         if (matched == 1) {
704             searchTime = min * 60;
705         } else if (matched == 2) {
706             searchTime = min * 60 + sec;
707         } else {
708             char buf[MSG_SIZ];
709             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
710             DisplayFatalError(buf, 0, 2);
711         }
712     }
713
714     /* [AS] Adjudication threshold */
715     adjudicateLossThreshold = appData.adjudicateLossThreshold;
716     
717     first.which = "first";
718     second.which = "second";
719     first.maybeThinking = second.maybeThinking = FALSE;
720     first.pr = second.pr = NoProc;
721     first.isr = second.isr = NULL;
722     first.sendTime = second.sendTime = 2;
723     first.sendDrawOffers = 1;
724     if (appData.firstPlaysBlack) {
725         first.twoMachinesColor = "black\n";
726         second.twoMachinesColor = "white\n";
727     } else {
728         first.twoMachinesColor = "white\n";
729         second.twoMachinesColor = "black\n";
730     }
731     first.program = appData.firstChessProgram;
732     second.program = appData.secondChessProgram;
733     first.host = appData.firstHost;
734     second.host = appData.secondHost;
735     first.dir = appData.firstDirectory;
736     second.dir = appData.secondDirectory;
737     first.other = &second;
738     second.other = &first;
739     first.initString = appData.initString;
740     second.initString = appData.secondInitString;
741     first.computerString = appData.firstComputerString;
742     second.computerString = appData.secondComputerString;
743     first.useSigint = second.useSigint = TRUE;
744     first.useSigterm = second.useSigterm = TRUE;
745     first.reuse = appData.reuseFirst;
746     second.reuse = appData.reuseSecond;
747     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
748     second.nps = appData.secondNPS;
749     first.useSetboard = second.useSetboard = FALSE;
750     first.useSAN = second.useSAN = FALSE;
751     first.usePing = second.usePing = FALSE;
752     first.lastPing = second.lastPing = 0;
753     first.lastPong = second.lastPong = 0;
754     first.usePlayother = second.usePlayother = FALSE;
755     first.useColors = second.useColors = TRUE;
756     first.useUsermove = second.useUsermove = FALSE;
757     first.sendICS = second.sendICS = FALSE;
758     first.sendName = second.sendName = appData.icsActive;
759     first.sdKludge = second.sdKludge = FALSE;
760     first.stKludge = second.stKludge = FALSE;
761     TidyProgramName(first.program, first.host, first.tidy);
762     TidyProgramName(second.program, second.host, second.tidy);
763     first.matchWins = second.matchWins = 0;
764     strcpy(first.variants, appData.variant);
765     strcpy(second.variants, appData.variant);
766     first.analysisSupport = second.analysisSupport = 2; /* detect */
767     first.analyzing = second.analyzing = FALSE;
768     first.initDone = second.initDone = FALSE;
769
770     /* New features added by Tord: */
771     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
772     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
773     /* End of new features added by Tord. */
774     first.fenOverride  = appData.fenOverride1;
775     second.fenOverride = appData.fenOverride2;
776
777     /* [HGM] time odds: set factor for each machine */
778     first.timeOdds  = appData.firstTimeOdds;
779     second.timeOdds = appData.secondTimeOdds;
780     { float norm = 1;
781         if(appData.timeOddsMode) {
782             norm = first.timeOdds;
783             if(norm > second.timeOdds) norm = second.timeOdds;
784         }
785         first.timeOdds /= norm;
786         second.timeOdds /= norm;
787     }
788
789     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
790     first.accumulateTC = appData.firstAccumulateTC;
791     second.accumulateTC = appData.secondAccumulateTC;
792     first.maxNrOfSessions = second.maxNrOfSessions = 1;
793
794     /* [HGM] debug */
795     first.debug = second.debug = FALSE;
796     first.supportsNPS = second.supportsNPS = UNKNOWN;
797
798     /* [HGM] options */
799     first.optionSettings  = appData.firstOptions;
800     second.optionSettings = appData.secondOptions;
801
802     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
803     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
804     first.isUCI = appData.firstIsUCI; /* [AS] */
805     second.isUCI = appData.secondIsUCI; /* [AS] */
806     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
807     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
808
809     if (appData.firstProtocolVersion > PROTOVER ||
810         appData.firstProtocolVersion < 1) {
811       char buf[MSG_SIZ];
812       sprintf(buf, _("protocol version %d not supported"),
813               appData.firstProtocolVersion);
814       DisplayFatalError(buf, 0, 2);
815     } else {
816       first.protocolVersion = appData.firstProtocolVersion;
817     }
818
819     if (appData.secondProtocolVersion > PROTOVER ||
820         appData.secondProtocolVersion < 1) {
821       char buf[MSG_SIZ];
822       sprintf(buf, _("protocol version %d not supported"),
823               appData.secondProtocolVersion);
824       DisplayFatalError(buf, 0, 2);
825     } else {
826       second.protocolVersion = appData.secondProtocolVersion;
827     }
828
829     if (appData.icsActive) {
830         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
831 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
832     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
833         appData.clockMode = FALSE;
834         first.sendTime = second.sendTime = 0;
835     }
836     
837 #if ZIPPY
838     /* Override some settings from environment variables, for backward
839        compatibility.  Unfortunately it's not feasible to have the env
840        vars just set defaults, at least in xboard.  Ugh.
841     */
842     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
843       ZippyInit();
844     }
845 #endif
846     
847     if (appData.noChessProgram) {
848         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
849         sprintf(programVersion, "%s", PACKAGE_STRING);
850     } else {
851       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
852       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
853       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
854     }
855
856     if (!appData.icsActive) {
857       char buf[MSG_SIZ];
858       /* Check for variants that are supported only in ICS mode,
859          or not at all.  Some that are accepted here nevertheless
860          have bugs; see comments below.
861       */
862       VariantClass variant = StringToVariant(appData.variant);
863       switch (variant) {
864       case VariantBughouse:     /* need four players and two boards */
865       case VariantKriegspiel:   /* need to hide pieces and move details */
866       /* case VariantFischeRandom: (Fabien: moved below) */
867         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
868         DisplayFatalError(buf, 0, 2);
869         return;
870
871       case VariantUnknown:
872       case VariantLoadable:
873       case Variant29:
874       case Variant30:
875       case Variant31:
876       case Variant32:
877       case Variant33:
878       case Variant34:
879       case Variant35:
880       case Variant36:
881       default:
882         sprintf(buf, _("Unknown variant name %s"), appData.variant);
883         DisplayFatalError(buf, 0, 2);
884         return;
885
886       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
887       case VariantFairy:      /* [HGM] TestLegality definitely off! */
888       case VariantGothic:     /* [HGM] should work */
889       case VariantCapablanca: /* [HGM] should work */
890       case VariantCourier:    /* [HGM] initial forced moves not implemented */
891       case VariantShogi:      /* [HGM] drops not tested for legality */
892       case VariantKnightmate: /* [HGM] should work */
893       case VariantCylinder:   /* [HGM] untested */
894       case VariantFalcon:     /* [HGM] untested */
895       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
896                                  offboard interposition not understood */
897       case VariantNormal:     /* definitely works! */
898       case VariantWildCastle: /* pieces not automatically shuffled */
899       case VariantNoCastle:   /* pieces not automatically shuffled */
900       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
901       case VariantLosers:     /* should work except for win condition,
902                                  and doesn't know captures are mandatory */
903       case VariantSuicide:    /* should work except for win condition,
904                                  and doesn't know captures are mandatory */
905       case VariantGiveaway:   /* should work except for win condition,
906                                  and doesn't know captures are mandatory */
907       case VariantTwoKings:   /* should work */
908       case VariantAtomic:     /* should work except for win condition */
909       case Variant3Check:     /* should work except for win condition */
910       case VariantShatranj:   /* should work except for all win conditions */
911       case VariantMakruk:     /* should work except for daw countdown */
912       case VariantBerolina:   /* might work if TestLegality is off */
913       case VariantCapaRandom: /* should work */
914       case VariantJanus:      /* should work */
915       case VariantSuper:      /* experimental */
916       case VariantGreat:      /* experimental, requires legality testing to be off */
917         break;
918       }
919     }
920
921     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
922     InitEngineUCI( installDir, &second );
923 }
924
925 int NextIntegerFromString( char ** str, long * value )
926 {
927     int result = -1;
928     char * s = *str;
929
930     while( *s == ' ' || *s == '\t' ) {
931         s++;
932     }
933
934     *value = 0;
935
936     if( *s >= '0' && *s <= '9' ) {
937         while( *s >= '0' && *s <= '9' ) {
938             *value = *value * 10 + (*s - '0');
939             s++;
940         }
941
942         result = 0;
943     }
944
945     *str = s;
946
947     return result;
948 }
949
950 int NextTimeControlFromString( char ** str, long * value )
951 {
952     long temp;
953     int result = NextIntegerFromString( str, &temp );
954
955     if( result == 0 ) {
956         *value = temp * 60; /* Minutes */
957         if( **str == ':' ) {
958             (*str)++;
959             result = NextIntegerFromString( str, &temp );
960             *value += temp; /* Seconds */
961         }
962     }
963
964     return result;
965 }
966
967 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
968 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
969     int result = -1; long temp, temp2;
970
971     if(**str != '+') return -1; // old params remain in force!
972     (*str)++;
973     if( NextTimeControlFromString( str, &temp ) ) return -1;
974
975     if(**str != '/') {
976         /* time only: incremental or sudden-death time control */
977         if(**str == '+') { /* increment follows; read it */
978             (*str)++;
979             if(result = NextIntegerFromString( str, &temp2)) return -1;
980             *inc = temp2 * 1000;
981         } else *inc = 0;
982         *moves = 0; *tc = temp * 1000; 
983         return 0;
984     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
985
986     (*str)++; /* classical time control */
987     result = NextTimeControlFromString( str, &temp2);
988     if(result == 0) {
989         *moves = temp/60;
990         *tc    = temp2 * 1000;
991         *inc   = 0;
992     }
993     return result;
994 }
995
996 int GetTimeQuota(int movenr)
997 {   /* [HGM] get time to add from the multi-session time-control string */
998     int moves=1; /* kludge to force reading of first session */
999     long time, increment;
1000     char *s = fullTimeControlString;
1001
1002     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
1003     do {
1004         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
1005         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1006         if(movenr == -1) return time;    /* last move before new session     */
1007         if(!moves) return increment;     /* current session is incremental   */
1008         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1009     } while(movenr >= -1);               /* try again for next session       */
1010
1011     return 0; // no new time quota on this move
1012 }
1013
1014 int
1015 ParseTimeControl(tc, ti, mps)
1016      char *tc;
1017      int ti;
1018      int mps;
1019 {
1020   long tc1;
1021   long tc2;
1022   char buf[MSG_SIZ];
1023   
1024   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1025   if(ti > 0) {
1026     if(mps)
1027       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1028     else sprintf(buf, "+%s+%d", tc, ti);
1029   } else {
1030     if(mps)
1031              sprintf(buf, "+%d/%s", mps, tc);
1032     else sprintf(buf, "+%s", tc);
1033   }
1034   fullTimeControlString = StrSave(buf);
1035   
1036   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1037     return FALSE;
1038   }
1039   
1040   if( *tc == '/' ) {
1041     /* Parse second time control */
1042     tc++;
1043     
1044     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1045       return FALSE;
1046     }
1047     
1048     if( tc2 == 0 ) {
1049       return FALSE;
1050     }
1051     
1052     timeControl_2 = tc2 * 1000;
1053   }
1054   else {
1055     timeControl_2 = 0;
1056   }
1057   
1058   if( tc1 == 0 ) {
1059     return FALSE;
1060   }
1061   
1062   timeControl = tc1 * 1000;
1063   
1064   if (ti >= 0) {
1065     timeIncrement = ti * 1000;  /* convert to ms */
1066     movesPerSession = 0;
1067   } else {
1068     timeIncrement = 0;
1069     movesPerSession = mps;
1070   }
1071   return TRUE;
1072 }
1073
1074 void
1075 InitBackEnd2()
1076 {
1077     if (appData.debugMode) {
1078         fprintf(debugFP, "%s\n", programVersion);
1079     }
1080
1081     set_cont_sequence(appData.wrapContSeq);
1082     if (appData.matchGames > 0) {
1083         appData.matchMode = TRUE;
1084     } else if (appData.matchMode) {
1085         appData.matchGames = 1;
1086     }
1087     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1088         appData.matchGames = appData.sameColorGames;
1089     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1090         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1091         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1092     }
1093     Reset(TRUE, FALSE);
1094     if (appData.noChessProgram || first.protocolVersion == 1) {
1095       InitBackEnd3();
1096     } else {
1097       /* kludge: allow timeout for initial "feature" commands */
1098       FreezeUI();
1099       DisplayMessage("", _("Starting chess program"));
1100       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1101     }
1102 }
1103
1104 void
1105 InitBackEnd3 P((void))
1106 {
1107     GameMode initialMode;
1108     char buf[MSG_SIZ];
1109     int err;
1110
1111     InitChessProgram(&first, startedFromSetupPosition);
1112
1113     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1114         free(programVersion);
1115         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1116         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1117     }
1118
1119     if (appData.icsActive) {
1120 #ifdef WIN32
1121         /* [DM] Make a console window if needed [HGM] merged ifs */
1122         ConsoleCreate(); 
1123 #endif
1124         err = establish();
1125         if (err != 0) {
1126             if (*appData.icsCommPort != NULLCHAR) {
1127                 sprintf(buf, _("Could not open comm port %s"),  
1128                         appData.icsCommPort);
1129             } else {
1130                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1131                         appData.icsHost, appData.icsPort);
1132             }
1133             DisplayFatalError(buf, err, 1);
1134             return;
1135         }
1136         SetICSMode();
1137         telnetISR =
1138           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1139         fromUserISR =
1140           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1141         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1142             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1143     } else if (appData.noChessProgram) {
1144         SetNCPMode();
1145     } else {
1146         SetGNUMode();
1147     }
1148
1149     if (*appData.cmailGameName != NULLCHAR) {
1150         SetCmailMode();
1151         OpenLoopback(&cmailPR);
1152         cmailISR =
1153           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1154     }
1155     
1156     ThawUI();
1157     DisplayMessage("", "");
1158     if (StrCaseCmp(appData.initialMode, "") == 0) {
1159       initialMode = BeginningOfGame;
1160     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1161       initialMode = TwoMachinesPlay;
1162     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1163       initialMode = AnalyzeFile; 
1164     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1165       initialMode = AnalyzeMode;
1166     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1167       initialMode = MachinePlaysWhite;
1168     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1169       initialMode = MachinePlaysBlack;
1170     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1171       initialMode = EditGame;
1172     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1173       initialMode = EditPosition;
1174     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1175       initialMode = Training;
1176     } else {
1177       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1178       DisplayFatalError(buf, 0, 2);
1179       return;
1180     }
1181
1182     if (appData.matchMode) {
1183         /* Set up machine vs. machine match */
1184         if (appData.noChessProgram) {
1185             DisplayFatalError(_("Can't have a match with no chess programs"),
1186                               0, 2);
1187             return;
1188         }
1189         matchMode = TRUE;
1190         matchGame = 1;
1191         if (*appData.loadGameFile != NULLCHAR) {
1192             int index = appData.loadGameIndex; // [HGM] autoinc
1193             if(index<0) lastIndex = index = 1;
1194             if (!LoadGameFromFile(appData.loadGameFile,
1195                                   index,
1196                                   appData.loadGameFile, FALSE)) {
1197                 DisplayFatalError(_("Bad game file"), 0, 1);
1198                 return;
1199             }
1200         } else if (*appData.loadPositionFile != NULLCHAR) {
1201             int index = appData.loadPositionIndex; // [HGM] autoinc
1202             if(index<0) lastIndex = index = 1;
1203             if (!LoadPositionFromFile(appData.loadPositionFile,
1204                                       index,
1205                                       appData.loadPositionFile)) {
1206                 DisplayFatalError(_("Bad position file"), 0, 1);
1207                 return;
1208             }
1209         }
1210         TwoMachinesEvent();
1211     } else if (*appData.cmailGameName != NULLCHAR) {
1212         /* Set up cmail mode */
1213         ReloadCmailMsgEvent(TRUE);
1214     } else {
1215         /* Set up other modes */
1216         if (initialMode == AnalyzeFile) {
1217           if (*appData.loadGameFile == NULLCHAR) {
1218             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1219             return;
1220           }
1221         }
1222         if (*appData.loadGameFile != NULLCHAR) {
1223             (void) LoadGameFromFile(appData.loadGameFile,
1224                                     appData.loadGameIndex,
1225                                     appData.loadGameFile, TRUE);
1226         } else if (*appData.loadPositionFile != NULLCHAR) {
1227             (void) LoadPositionFromFile(appData.loadPositionFile,
1228                                         appData.loadPositionIndex,
1229                                         appData.loadPositionFile);
1230             /* [HGM] try to make self-starting even after FEN load */
1231             /* to allow automatic setup of fairy variants with wtm */
1232             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1233                 gameMode = BeginningOfGame;
1234                 setboardSpoiledMachineBlack = 1;
1235             }
1236             /* [HGM] loadPos: make that every new game uses the setup */
1237             /* from file as long as we do not switch variant          */
1238             if(!blackPlaysFirst) {
1239                 startedFromPositionFile = TRUE;
1240                 CopyBoard(filePosition, boards[0]);
1241             }
1242         }
1243         if (initialMode == AnalyzeMode) {
1244           if (appData.noChessProgram) {
1245             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1246             return;
1247           }
1248           if (appData.icsActive) {
1249             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1250             return;
1251           }
1252           AnalyzeModeEvent();
1253         } else if (initialMode == AnalyzeFile) {
1254           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1255           ShowThinkingEvent();
1256           AnalyzeFileEvent();
1257           AnalysisPeriodicEvent(1);
1258         } else if (initialMode == MachinePlaysWhite) {
1259           if (appData.noChessProgram) {
1260             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1261                               0, 2);
1262             return;
1263           }
1264           if (appData.icsActive) {
1265             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1266                               0, 2);
1267             return;
1268           }
1269           MachineWhiteEvent();
1270         } else if (initialMode == MachinePlaysBlack) {
1271           if (appData.noChessProgram) {
1272             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1273                               0, 2);
1274             return;
1275           }
1276           if (appData.icsActive) {
1277             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1278                               0, 2);
1279             return;
1280           }
1281           MachineBlackEvent();
1282         } else if (initialMode == TwoMachinesPlay) {
1283           if (appData.noChessProgram) {
1284             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1285                               0, 2);
1286             return;
1287           }
1288           if (appData.icsActive) {
1289             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1290                               0, 2);
1291             return;
1292           }
1293           TwoMachinesEvent();
1294         } else if (initialMode == EditGame) {
1295           EditGameEvent();
1296         } else if (initialMode == EditPosition) {
1297           EditPositionEvent();
1298         } else if (initialMode == Training) {
1299           if (*appData.loadGameFile == NULLCHAR) {
1300             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1301             return;
1302           }
1303           TrainingEvent();
1304         }
1305     }
1306 }
1307
1308 /*
1309  * Establish will establish a contact to a remote host.port.
1310  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1311  *  used to talk to the host.
1312  * Returns 0 if okay, error code if not.
1313  */
1314 int
1315 establish()
1316 {
1317     char buf[MSG_SIZ];
1318
1319     if (*appData.icsCommPort != NULLCHAR) {
1320         /* Talk to the host through a serial comm port */
1321         return OpenCommPort(appData.icsCommPort, &icsPR);
1322
1323     } else if (*appData.gateway != NULLCHAR) {
1324         if (*appData.remoteShell == NULLCHAR) {
1325             /* Use the rcmd protocol to run telnet program on a gateway host */
1326             snprintf(buf, sizeof(buf), "%s %s %s",
1327                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1328             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1329
1330         } else {
1331             /* Use the rsh program to run telnet program on a gateway host */
1332             if (*appData.remoteUser == NULLCHAR) {
1333                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1334                         appData.gateway, appData.telnetProgram,
1335                         appData.icsHost, appData.icsPort);
1336             } else {
1337                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1338                         appData.remoteShell, appData.gateway, 
1339                         appData.remoteUser, appData.telnetProgram,
1340                         appData.icsHost, appData.icsPort);
1341             }
1342             return StartChildProcess(buf, "", &icsPR);
1343
1344         }
1345     } else if (appData.useTelnet) {
1346         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1347
1348     } else {
1349         /* TCP socket interface differs somewhat between
1350            Unix and NT; handle details in the front end.
1351            */
1352         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1353     }
1354 }
1355
1356 void EscapeExpand(char *p, char *q)
1357 {       // [HGM] initstring: routine to shape up string arguments
1358         while(*p++ = *q++) if(p[-1] == '\\')
1359             switch(*q++) {
1360                 case 'n': p[-1] = '\n'; break;
1361                 case 'r': p[-1] = '\r'; break;
1362                 case 't': p[-1] = '\t'; break;
1363                 case '\\': p[-1] = '\\'; break;
1364                 case 0: *p = 0; return;
1365                 default: p[-1] = q[-1]; break;
1366             }
1367 }
1368
1369 void
1370 show_bytes(fp, buf, count)
1371      FILE *fp;
1372      char *buf;
1373      int count;
1374 {
1375     while (count--) {
1376         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1377             fprintf(fp, "\\%03o", *buf & 0xff);
1378         } else {
1379             putc(*buf, fp);
1380         }
1381         buf++;
1382     }
1383     fflush(fp);
1384 }
1385
1386 /* Returns an errno value */
1387 int
1388 OutputMaybeTelnet(pr, message, count, outError)
1389      ProcRef pr;
1390      char *message;
1391      int count;
1392      int *outError;
1393 {
1394     char buf[8192], *p, *q, *buflim;
1395     int left, newcount, outcount;
1396
1397     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1398         *appData.gateway != NULLCHAR) {
1399         if (appData.debugMode) {
1400             fprintf(debugFP, ">ICS: ");
1401             show_bytes(debugFP, message, count);
1402             fprintf(debugFP, "\n");
1403         }
1404         return OutputToProcess(pr, message, count, outError);
1405     }
1406
1407     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1408     p = message;
1409     q = buf;
1410     left = count;
1411     newcount = 0;
1412     while (left) {
1413         if (q >= buflim) {
1414             if (appData.debugMode) {
1415                 fprintf(debugFP, ">ICS: ");
1416                 show_bytes(debugFP, buf, newcount);
1417                 fprintf(debugFP, "\n");
1418             }
1419             outcount = OutputToProcess(pr, buf, newcount, outError);
1420             if (outcount < newcount) return -1; /* to be sure */
1421             q = buf;
1422             newcount = 0;
1423         }
1424         if (*p == '\n') {
1425             *q++ = '\r';
1426             newcount++;
1427         } else if (((unsigned char) *p) == TN_IAC) {
1428             *q++ = (char) TN_IAC;
1429             newcount ++;
1430         }
1431         *q++ = *p++;
1432         newcount++;
1433         left--;
1434     }
1435     if (appData.debugMode) {
1436         fprintf(debugFP, ">ICS: ");
1437         show_bytes(debugFP, buf, newcount);
1438         fprintf(debugFP, "\n");
1439     }
1440     outcount = OutputToProcess(pr, buf, newcount, outError);
1441     if (outcount < newcount) return -1; /* to be sure */
1442     return count;
1443 }
1444
1445 void
1446 read_from_player(isr, closure, message, count, error)
1447      InputSourceRef isr;
1448      VOIDSTAR closure;
1449      char *message;
1450      int count;
1451      int error;
1452 {
1453     int outError, outCount;
1454     static int gotEof = 0;
1455
1456     /* Pass data read from player on to ICS */
1457     if (count > 0) {
1458         gotEof = 0;
1459         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1460         if (outCount < count) {
1461             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1462         }
1463     } else if (count < 0) {
1464         RemoveInputSource(isr);
1465         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1466     } else if (gotEof++ > 0) {
1467         RemoveInputSource(isr);
1468         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1469     }
1470 }
1471
1472 void
1473 KeepAlive()
1474 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1475     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1476     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1477     SendToICS("date\n");
1478     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1479 }
1480
1481 /* added routine for printf style output to ics */
1482 void ics_printf(char *format, ...)
1483 {
1484     char buffer[MSG_SIZ];
1485     va_list args;
1486
1487     va_start(args, format);
1488     vsnprintf(buffer, sizeof(buffer), format, args);
1489     buffer[sizeof(buffer)-1] = '\0';
1490     SendToICS(buffer);
1491     va_end(args);
1492 }
1493
1494 void
1495 SendToICS(s)
1496      char *s;
1497 {
1498     int count, outCount, outError;
1499
1500     if (icsPR == NULL) return;
1501
1502     count = strlen(s);
1503     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1504     if (outCount < count) {
1505         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1506     }
1507 }
1508
1509 /* This is used for sending logon scripts to the ICS. Sending
1510    without a delay causes problems when using timestamp on ICC
1511    (at least on my machine). */
1512 void
1513 SendToICSDelayed(s,msdelay)
1514      char *s;
1515      long msdelay;
1516 {
1517     int count, outCount, outError;
1518
1519     if (icsPR == NULL) return;
1520
1521     count = strlen(s);
1522     if (appData.debugMode) {
1523         fprintf(debugFP, ">ICS: ");
1524         show_bytes(debugFP, s, count);
1525         fprintf(debugFP, "\n");
1526     }
1527     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1528                                       msdelay);
1529     if (outCount < count) {
1530         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1531     }
1532 }
1533
1534
1535 /* Remove all highlighting escape sequences in s
1536    Also deletes any suffix starting with '(' 
1537    */
1538 char *
1539 StripHighlightAndTitle(s)
1540      char *s;
1541 {
1542     static char retbuf[MSG_SIZ];
1543     char *p = retbuf;
1544
1545     while (*s != NULLCHAR) {
1546         while (*s == '\033') {
1547             while (*s != NULLCHAR && !isalpha(*s)) s++;
1548             if (*s != NULLCHAR) s++;
1549         }
1550         while (*s != NULLCHAR && *s != '\033') {
1551             if (*s == '(' || *s == '[') {
1552                 *p = NULLCHAR;
1553                 return retbuf;
1554             }
1555             *p++ = *s++;
1556         }
1557     }
1558     *p = NULLCHAR;
1559     return retbuf;
1560 }
1561
1562 /* Remove all highlighting escape sequences in s */
1563 char *
1564 StripHighlight(s)
1565      char *s;
1566 {
1567     static char retbuf[MSG_SIZ];
1568     char *p = retbuf;
1569
1570     while (*s != NULLCHAR) {
1571         while (*s == '\033') {
1572             while (*s != NULLCHAR && !isalpha(*s)) s++;
1573             if (*s != NULLCHAR) s++;
1574         }
1575         while (*s != NULLCHAR && *s != '\033') {
1576             *p++ = *s++;
1577         }
1578     }
1579     *p = NULLCHAR;
1580     return retbuf;
1581 }
1582
1583 char *variantNames[] = VARIANT_NAMES;
1584 char *
1585 VariantName(v)
1586      VariantClass v;
1587 {
1588     return variantNames[v];
1589 }
1590
1591
1592 /* Identify a variant from the strings the chess servers use or the
1593    PGN Variant tag names we use. */
1594 VariantClass
1595 StringToVariant(e)
1596      char *e;
1597 {
1598     char *p;
1599     int wnum = -1;
1600     VariantClass v = VariantNormal;
1601     int i, found = FALSE;
1602     char buf[MSG_SIZ];
1603
1604     if (!e) return v;
1605
1606     /* [HGM] skip over optional board-size prefixes */
1607     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1608         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1609         while( *e++ != '_');
1610     }
1611
1612     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1613         v = VariantNormal;
1614         found = TRUE;
1615     } else
1616     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1617       if (StrCaseStr(e, variantNames[i])) {
1618         v = (VariantClass) i;
1619         found = TRUE;
1620         break;
1621       }
1622     }
1623
1624     if (!found) {
1625       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1626           || StrCaseStr(e, "wild/fr") 
1627           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1628         v = VariantFischeRandom;
1629       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1630                  (i = 1, p = StrCaseStr(e, "w"))) {
1631         p += i;
1632         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1633         if (isdigit(*p)) {
1634           wnum = atoi(p);
1635         } else {
1636           wnum = -1;
1637         }
1638         switch (wnum) {
1639         case 0: /* FICS only, actually */
1640         case 1:
1641           /* Castling legal even if K starts on d-file */
1642           v = VariantWildCastle;
1643           break;
1644         case 2:
1645         case 3:
1646         case 4:
1647           /* Castling illegal even if K & R happen to start in
1648              normal positions. */
1649           v = VariantNoCastle;
1650           break;
1651         case 5:
1652         case 7:
1653         case 8:
1654         case 10:
1655         case 11:
1656         case 12:
1657         case 13:
1658         case 14:
1659         case 15:
1660         case 18:
1661         case 19:
1662           /* Castling legal iff K & R start in normal positions */
1663           v = VariantNormal;
1664           break;
1665         case 6:
1666         case 20:
1667         case 21:
1668           /* Special wilds for position setup; unclear what to do here */
1669           v = VariantLoadable;
1670           break;
1671         case 9:
1672           /* Bizarre ICC game */
1673           v = VariantTwoKings;
1674           break;
1675         case 16:
1676           v = VariantKriegspiel;
1677           break;
1678         case 17:
1679           v = VariantLosers;
1680           break;
1681         case 22:
1682           v = VariantFischeRandom;
1683           break;
1684         case 23:
1685           v = VariantCrazyhouse;
1686           break;
1687         case 24:
1688           v = VariantBughouse;
1689           break;
1690         case 25:
1691           v = Variant3Check;
1692           break;
1693         case 26:
1694           /* Not quite the same as FICS suicide! */
1695           v = VariantGiveaway;
1696           break;
1697         case 27:
1698           v = VariantAtomic;
1699           break;
1700         case 28:
1701           v = VariantShatranj;
1702           break;
1703
1704         /* Temporary names for future ICC types.  The name *will* change in 
1705            the next xboard/WinBoard release after ICC defines it. */
1706         case 29:
1707           v = Variant29;
1708           break;
1709         case 30:
1710           v = Variant30;
1711           break;
1712         case 31:
1713           v = Variant31;
1714           break;
1715         case 32:
1716           v = Variant32;
1717           break;
1718         case 33:
1719           v = Variant33;
1720           break;
1721         case 34:
1722           v = Variant34;
1723           break;
1724         case 35:
1725           v = Variant35;
1726           break;
1727         case 36:
1728           v = Variant36;
1729           break;
1730         case 37:
1731           v = VariantShogi;
1732           break;
1733         case 38:
1734           v = VariantXiangqi;
1735           break;
1736         case 39:
1737           v = VariantCourier;
1738           break;
1739         case 40:
1740           v = VariantGothic;
1741           break;
1742         case 41:
1743           v = VariantCapablanca;
1744           break;
1745         case 42:
1746           v = VariantKnightmate;
1747           break;
1748         case 43:
1749           v = VariantFairy;
1750           break;
1751         case 44:
1752           v = VariantCylinder;
1753           break;
1754         case 45:
1755           v = VariantFalcon;
1756           break;
1757         case 46:
1758           v = VariantCapaRandom;
1759           break;
1760         case 47:
1761           v = VariantBerolina;
1762           break;
1763         case 48:
1764           v = VariantJanus;
1765           break;
1766         case 49:
1767           v = VariantSuper;
1768           break;
1769         case 50:
1770           v = VariantGreat;
1771           break;
1772         case -1:
1773           /* Found "wild" or "w" in the string but no number;
1774              must assume it's normal chess. */
1775           v = VariantNormal;
1776           break;
1777         default:
1778           sprintf(buf, _("Unknown wild type %d"), wnum);
1779           DisplayError(buf, 0);
1780           v = VariantUnknown;
1781           break;
1782         }
1783       }
1784     }
1785     if (appData.debugMode) {
1786       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1787               e, wnum, VariantName(v));
1788     }
1789     return v;
1790 }
1791
1792 static int leftover_start = 0, leftover_len = 0;
1793 char star_match[STAR_MATCH_N][MSG_SIZ];
1794
1795 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1796    advance *index beyond it, and set leftover_start to the new value of
1797    *index; else return FALSE.  If pattern contains the character '*', it
1798    matches any sequence of characters not containing '\r', '\n', or the
1799    character following the '*' (if any), and the matched sequence(s) are
1800    copied into star_match.
1801    */
1802 int
1803 looking_at(buf, index, pattern)
1804      char *buf;
1805      int *index;
1806      char *pattern;
1807 {
1808     char *bufp = &buf[*index], *patternp = pattern;
1809     int star_count = 0;
1810     char *matchp = star_match[0];
1811     
1812     for (;;) {
1813         if (*patternp == NULLCHAR) {
1814             *index = leftover_start = bufp - buf;
1815             *matchp = NULLCHAR;
1816             return TRUE;
1817         }
1818         if (*bufp == NULLCHAR) return FALSE;
1819         if (*patternp == '*') {
1820             if (*bufp == *(patternp + 1)) {
1821                 *matchp = NULLCHAR;
1822                 matchp = star_match[++star_count];
1823                 patternp += 2;
1824                 bufp++;
1825                 continue;
1826             } else if (*bufp == '\n' || *bufp == '\r') {
1827                 patternp++;
1828                 if (*patternp == NULLCHAR)
1829                   continue;
1830                 else
1831                   return FALSE;
1832             } else {
1833                 *matchp++ = *bufp++;
1834                 continue;
1835             }
1836         }
1837         if (*patternp != *bufp) return FALSE;
1838         patternp++;
1839         bufp++;
1840     }
1841 }
1842
1843 void
1844 SendToPlayer(data, length)
1845      char *data;
1846      int length;
1847 {
1848     int error, outCount;
1849     outCount = OutputToProcess(NoProc, data, length, &error);
1850     if (outCount < length) {
1851         DisplayFatalError(_("Error writing to display"), error, 1);
1852     }
1853 }
1854
1855 void
1856 PackHolding(packed, holding)
1857      char packed[];
1858      char *holding;
1859 {
1860     char *p = holding;
1861     char *q = packed;
1862     int runlength = 0;
1863     int curr = 9999;
1864     do {
1865         if (*p == curr) {
1866             runlength++;
1867         } else {
1868             switch (runlength) {
1869               case 0:
1870                 break;
1871               case 1:
1872                 *q++ = curr;
1873                 break;
1874               case 2:
1875                 *q++ = curr;
1876                 *q++ = curr;
1877                 break;
1878               default:
1879                 sprintf(q, "%d", runlength);
1880                 while (*q) q++;
1881                 *q++ = curr;
1882                 break;
1883             }
1884             runlength = 1;
1885             curr = *p;
1886         }
1887     } while (*p++);
1888     *q = NULLCHAR;
1889 }
1890
1891 /* Telnet protocol requests from the front end */
1892 void
1893 TelnetRequest(ddww, option)
1894      unsigned char ddww, option;
1895 {
1896     unsigned char msg[3];
1897     int outCount, outError;
1898
1899     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1900
1901     if (appData.debugMode) {
1902         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1903         switch (ddww) {
1904           case TN_DO:
1905             ddwwStr = "DO";
1906             break;
1907           case TN_DONT:
1908             ddwwStr = "DONT";
1909             break;
1910           case TN_WILL:
1911             ddwwStr = "WILL";
1912             break;
1913           case TN_WONT:
1914             ddwwStr = "WONT";
1915             break;
1916           default:
1917             ddwwStr = buf1;
1918             sprintf(buf1, "%d", ddww);
1919             break;
1920         }
1921         switch (option) {
1922           case TN_ECHO:
1923             optionStr = "ECHO";
1924             break;
1925           default:
1926             optionStr = buf2;
1927             sprintf(buf2, "%d", option);
1928             break;
1929         }
1930         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1931     }
1932     msg[0] = TN_IAC;
1933     msg[1] = ddww;
1934     msg[2] = option;
1935     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1936     if (outCount < 3) {
1937         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1938     }
1939 }
1940
1941 void
1942 DoEcho()
1943 {
1944     if (!appData.icsActive) return;
1945     TelnetRequest(TN_DO, TN_ECHO);
1946 }
1947
1948 void
1949 DontEcho()
1950 {
1951     if (!appData.icsActive) return;
1952     TelnetRequest(TN_DONT, TN_ECHO);
1953 }
1954
1955 void
1956 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1957 {
1958     /* put the holdings sent to us by the server on the board holdings area */
1959     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1960     char p;
1961     ChessSquare piece;
1962
1963     if(gameInfo.holdingsWidth < 2)  return;
1964     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1965         return; // prevent overwriting by pre-board holdings
1966
1967     if( (int)lowestPiece >= BlackPawn ) {
1968         holdingsColumn = 0;
1969         countsColumn = 1;
1970         holdingsStartRow = BOARD_HEIGHT-1;
1971         direction = -1;
1972     } else {
1973         holdingsColumn = BOARD_WIDTH-1;
1974         countsColumn = BOARD_WIDTH-2;
1975         holdingsStartRow = 0;
1976         direction = 1;
1977     }
1978
1979     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1980         board[i][holdingsColumn] = EmptySquare;
1981         board[i][countsColumn]   = (ChessSquare) 0;
1982     }
1983     while( (p=*holdings++) != NULLCHAR ) {
1984         piece = CharToPiece( ToUpper(p) );
1985         if(piece == EmptySquare) continue;
1986         /*j = (int) piece - (int) WhitePawn;*/
1987         j = PieceToNumber(piece);
1988         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1989         if(j < 0) continue;               /* should not happen */
1990         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1991         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1992         board[holdingsStartRow+j*direction][countsColumn]++;
1993     }
1994 }
1995
1996
1997 void
1998 VariantSwitch(Board board, VariantClass newVariant)
1999 {
2000    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2001    static Board oldBoard;
2002
2003    startedFromPositionFile = FALSE;
2004    if(gameInfo.variant == newVariant) return;
2005
2006    /* [HGM] This routine is called each time an assignment is made to
2007     * gameInfo.variant during a game, to make sure the board sizes
2008     * are set to match the new variant. If that means adding or deleting
2009     * holdings, we shift the playing board accordingly
2010     * This kludge is needed because in ICS observe mode, we get boards
2011     * of an ongoing game without knowing the variant, and learn about the
2012     * latter only later. This can be because of the move list we requested,
2013     * in which case the game history is refilled from the beginning anyway,
2014     * but also when receiving holdings of a crazyhouse game. In the latter
2015     * case we want to add those holdings to the already received position.
2016     */
2017
2018    
2019    if (appData.debugMode) {
2020      fprintf(debugFP, "Switch board from %s to %s\n",
2021              VariantName(gameInfo.variant), VariantName(newVariant));
2022      setbuf(debugFP, NULL);
2023    }
2024    shuffleOpenings = 0;       /* [HGM] shuffle */
2025    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2026    switch(newVariant) 
2027      {
2028      case VariantShogi:
2029        newWidth = 9;  newHeight = 9;
2030        gameInfo.holdingsSize = 7;
2031      case VariantBughouse:
2032      case VariantCrazyhouse:
2033        newHoldingsWidth = 2; break;
2034      case VariantGreat:
2035        newWidth = 10;
2036      case VariantSuper:
2037        newHoldingsWidth = 2;
2038        gameInfo.holdingsSize = 8;
2039        break;
2040      case VariantGothic:
2041      case VariantCapablanca:
2042      case VariantCapaRandom:
2043        newWidth = 10;
2044      default:
2045        newHoldingsWidth = gameInfo.holdingsSize = 0;
2046      };
2047    
2048    if(newWidth  != gameInfo.boardWidth  ||
2049       newHeight != gameInfo.boardHeight ||
2050       newHoldingsWidth != gameInfo.holdingsWidth ) {
2051      
2052      /* shift position to new playing area, if needed */
2053      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2054        for(i=0; i<BOARD_HEIGHT; i++) 
2055          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2056            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2057              board[i][j];
2058        for(i=0; i<newHeight; i++) {
2059          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2060          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2061        }
2062      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2063        for(i=0; i<BOARD_HEIGHT; i++)
2064          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2065            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2066              board[i][j];
2067      }
2068      gameInfo.boardWidth  = newWidth;
2069      gameInfo.boardHeight = newHeight;
2070      gameInfo.holdingsWidth = newHoldingsWidth;
2071      gameInfo.variant = newVariant;
2072      InitDrawingSizes(-2, 0);
2073    } else gameInfo.variant = newVariant;
2074    CopyBoard(oldBoard, board);   // remember correctly formatted board
2075      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2076    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2077 }
2078
2079 static int loggedOn = FALSE;
2080
2081 /*-- Game start info cache: --*/
2082 int gs_gamenum;
2083 char gs_kind[MSG_SIZ];
2084 static char player1Name[128] = "";
2085 static char player2Name[128] = "";
2086 static char cont_seq[] = "\n\\   ";
2087 static int player1Rating = -1;
2088 static int player2Rating = -1;
2089 /*----------------------------*/
2090
2091 ColorClass curColor = ColorNormal;
2092 int suppressKibitz = 0;
2093
2094 // [HGM] seekgraph
2095 Boolean soughtPending = FALSE;
2096 Boolean seekGraphUp;
2097 #define MAX_SEEK_ADS 200
2098 #define SQUARE 0x80
2099 char *seekAdList[MAX_SEEK_ADS];
2100 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2101 float tcList[MAX_SEEK_ADS];
2102 char colorList[MAX_SEEK_ADS];
2103 int nrOfSeekAds = 0;
2104 int minRating = 1010, maxRating = 2800;
2105 int hMargin = 10, vMargin = 20, h, w;
2106 extern int squareSize, lineGap;
2107
2108 void
2109 PlotSeekAd(int i)
2110 {
2111         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2112         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2113         if(r < minRating+100 && r >=0 ) r = minRating+100;
2114         if(r > maxRating) r = maxRating;
2115         if(tc < 1.) tc = 1.;
2116         if(tc > 95.) tc = 95.;
2117         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2118         y = ((double)r - minRating)/(maxRating - minRating)
2119             * (h-vMargin-squareSize/8-1) + vMargin;
2120         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2121         if(strstr(seekAdList[i], " u ")) color = 1;
2122         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2123            !strstr(seekAdList[i], "bullet") &&
2124            !strstr(seekAdList[i], "blitz") &&
2125            !strstr(seekAdList[i], "standard") ) color = 2;
2126         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2127         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2128 }
2129
2130 void
2131 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2132 {
2133         char buf[MSG_SIZ], *ext = "";
2134         VariantClass v = StringToVariant(type);
2135         if(strstr(type, "wild")) {
2136             ext = type + 4; // append wild number
2137             if(v == VariantFischeRandom) type = "chess960"; else
2138             if(v == VariantLoadable) type = "setup"; else
2139             type = VariantName(v);
2140         }
2141         sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2142         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2143             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2144             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2145             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2146             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2147             seekNrList[nrOfSeekAds] = nr;
2148             zList[nrOfSeekAds] = 0;
2149             seekAdList[nrOfSeekAds++] = StrSave(buf);
2150             if(plot) PlotSeekAd(nrOfSeekAds-1);
2151         }
2152 }
2153
2154 void
2155 EraseSeekDot(int i)
2156 {
2157     int x = xList[i], y = yList[i], d=squareSize/4, k;
2158     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2159     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2160     // now replot every dot that overlapped
2161     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2162         int xx = xList[k], yy = yList[k];
2163         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2164             DrawSeekDot(xx, yy, colorList[k]);
2165     }
2166 }
2167
2168 void
2169 RemoveSeekAd(int nr)
2170 {
2171         int i;
2172         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2173             EraseSeekDot(i);
2174             if(seekAdList[i]) free(seekAdList[i]);
2175             seekAdList[i] = seekAdList[--nrOfSeekAds];
2176             seekNrList[i] = seekNrList[nrOfSeekAds];
2177             ratingList[i] = ratingList[nrOfSeekAds];
2178             colorList[i]  = colorList[nrOfSeekAds];
2179             tcList[i] = tcList[nrOfSeekAds];
2180             xList[i]  = xList[nrOfSeekAds];
2181             yList[i]  = yList[nrOfSeekAds];
2182             zList[i]  = zList[nrOfSeekAds];
2183             seekAdList[nrOfSeekAds] = NULL;
2184             break;
2185         }
2186 }
2187
2188 Boolean
2189 MatchSoughtLine(char *line)
2190 {
2191     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2192     int nr, base, inc, u=0; char dummy;
2193
2194     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2195        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2196        (u=1) &&
2197        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2198         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2199         // match: compact and save the line
2200         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2201         return TRUE;
2202     }
2203     return FALSE;
2204 }
2205
2206 int
2207 DrawSeekGraph()
2208 {
2209     if(!seekGraphUp) return FALSE;
2210     int i;
2211     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2212     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2213
2214     DrawSeekBackground(0, 0, w, h);
2215     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2216     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2217     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2218         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2219         yy = h-1-yy;
2220         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2221         if(i%500 == 0) {
2222             char buf[MSG_SIZ];
2223             sprintf(buf, "%d", i);
2224             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2225         }
2226     }
2227     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2228     for(i=1; i<100; i+=(i<10?1:5)) {
2229         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2230         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2231         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2232             char buf[MSG_SIZ];
2233             sprintf(buf, "%d", i);
2234             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2235         }
2236     }
2237     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2238     return TRUE;
2239 }
2240
2241 int SeekGraphClick(ClickType click, int x, int y, int moving)
2242 {
2243     static int lastDown = 0, displayed = 0, lastSecond;
2244     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2245         if(click == Release || moving) return FALSE;
2246         nrOfSeekAds = 0;
2247         soughtPending = TRUE;
2248         SendToICS(ics_prefix);
2249         SendToICS("sought\n"); // should this be "sought all"?
2250     } else { // issue challenge based on clicked ad
2251         int dist = 10000; int i, closest = 0, second = 0;
2252         for(i=0; i<nrOfSeekAds; i++) {
2253             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2254             if(d < dist) { dist = d; closest = i; }
2255             second += (d - zList[i] < 120); // count in-range ads
2256             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2257         }
2258         if(dist < 120) {
2259             char buf[MSG_SIZ];
2260             second = (second > 1);
2261             if(displayed != closest || second != lastSecond) {
2262                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2263                 lastSecond = second; displayed = closest;
2264             }
2265             if(click == Press) {
2266                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2267                 lastDown = closest;
2268                 return TRUE;
2269             } // on press 'hit', only show info
2270             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2271             sprintf(buf, "play %d\n", seekNrList[closest]);
2272             SendToICS(ics_prefix);
2273             SendToICS(buf);
2274             return TRUE; // let incoming board of started game pop down the graph
2275         } else if(click == Release) { // release 'miss' is ignored
2276             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2277             if(moving == 2) { // right up-click
2278                 nrOfSeekAds = 0; // refresh graph
2279                 soughtPending = TRUE;
2280                 SendToICS(ics_prefix);
2281                 SendToICS("sought\n"); // should this be "sought all"?
2282             }
2283             return TRUE;
2284         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2285         // press miss or release hit 'pop down' seek graph
2286         seekGraphUp = FALSE;
2287         DrawPosition(TRUE, NULL);
2288     }
2289     return TRUE;
2290 }
2291
2292 void
2293 read_from_ics(isr, closure, data, count, error)
2294      InputSourceRef isr;
2295      VOIDSTAR closure;
2296      char *data;
2297      int count;
2298      int error;
2299 {
2300 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2301 #define STARTED_NONE 0
2302 #define STARTED_MOVES 1
2303 #define STARTED_BOARD 2
2304 #define STARTED_OBSERVE 3
2305 #define STARTED_HOLDINGS 4
2306 #define STARTED_CHATTER 5
2307 #define STARTED_COMMENT 6
2308 #define STARTED_MOVES_NOHIDE 7
2309     
2310     static int started = STARTED_NONE;
2311     static char parse[20000];
2312     static int parse_pos = 0;
2313     static char buf[BUF_SIZE + 1];
2314     static int firstTime = TRUE, intfSet = FALSE;
2315     static ColorClass prevColor = ColorNormal;
2316     static int savingComment = FALSE;
2317     static int cmatch = 0; // continuation sequence match
2318     char *bp;
2319     char str[500];
2320     int i, oldi;
2321     int buf_len;
2322     int next_out;
2323     int tkind;
2324     int backup;    /* [DM] For zippy color lines */
2325     char *p;
2326     char talker[MSG_SIZ]; // [HGM] chat
2327     int channel;
2328
2329     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2330
2331     if (appData.debugMode) {
2332       if (!error) {
2333         fprintf(debugFP, "<ICS: ");
2334         show_bytes(debugFP, data, count);
2335         fprintf(debugFP, "\n");
2336       }
2337     }
2338
2339     if (appData.debugMode) { int f = forwardMostMove;
2340         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2341                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2342                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2343     }
2344     if (count > 0) {
2345         /* If last read ended with a partial line that we couldn't parse,
2346            prepend it to the new read and try again. */
2347         if (leftover_len > 0) {
2348             for (i=0; i<leftover_len; i++)
2349               buf[i] = buf[leftover_start + i];
2350         }
2351
2352     /* copy new characters into the buffer */
2353     bp = buf + leftover_len;
2354     buf_len=leftover_len;
2355     for (i=0; i<count; i++)
2356     {
2357         // ignore these
2358         if (data[i] == '\r')
2359             continue;
2360
2361         // join lines split by ICS?
2362         if (!appData.noJoin)
2363         {
2364             /*
2365                 Joining just consists of finding matches against the
2366                 continuation sequence, and discarding that sequence
2367                 if found instead of copying it.  So, until a match
2368                 fails, there's nothing to do since it might be the
2369                 complete sequence, and thus, something we don't want
2370                 copied.
2371             */
2372             if (data[i] == cont_seq[cmatch])
2373             {
2374                 cmatch++;
2375                 if (cmatch == strlen(cont_seq))
2376                 {
2377                     cmatch = 0; // complete match.  just reset the counter
2378
2379                     /*
2380                         it's possible for the ICS to not include the space
2381                         at the end of the last word, making our [correct]
2382                         join operation fuse two separate words.  the server
2383                         does this when the space occurs at the width setting.
2384                     */
2385                     if (!buf_len || buf[buf_len-1] != ' ')
2386                     {
2387                         *bp++ = ' ';
2388                         buf_len++;
2389                     }
2390                 }
2391                 continue;
2392             }
2393             else if (cmatch)
2394             {
2395                 /*
2396                     match failed, so we have to copy what matched before
2397                     falling through and copying this character.  In reality,
2398                     this will only ever be just the newline character, but
2399                     it doesn't hurt to be precise.
2400                 */
2401                 strncpy(bp, cont_seq, cmatch);
2402                 bp += cmatch;
2403                 buf_len += cmatch;
2404                 cmatch = 0;
2405             }
2406         }
2407
2408         // copy this char
2409         *bp++ = data[i];
2410         buf_len++;
2411     }
2412
2413         buf[buf_len] = NULLCHAR;
2414 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2415         next_out = 0;
2416         leftover_start = 0;
2417         
2418         i = 0;
2419         while (i < buf_len) {
2420             /* Deal with part of the TELNET option negotiation
2421                protocol.  We refuse to do anything beyond the
2422                defaults, except that we allow the WILL ECHO option,
2423                which ICS uses to turn off password echoing when we are
2424                directly connected to it.  We reject this option
2425                if localLineEditing mode is on (always on in xboard)
2426                and we are talking to port 23, which might be a real
2427                telnet server that will try to keep WILL ECHO on permanently.
2428              */
2429             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2430                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2431                 unsigned char option;
2432                 oldi = i;
2433                 switch ((unsigned char) buf[++i]) {
2434                   case TN_WILL:
2435                     if (appData.debugMode)
2436                       fprintf(debugFP, "\n<WILL ");
2437                     switch (option = (unsigned char) buf[++i]) {
2438                       case TN_ECHO:
2439                         if (appData.debugMode)
2440                           fprintf(debugFP, "ECHO ");
2441                         /* Reply only if this is a change, according
2442                            to the protocol rules. */
2443                         if (remoteEchoOption) break;
2444                         if (appData.localLineEditing &&
2445                             atoi(appData.icsPort) == TN_PORT) {
2446                             TelnetRequest(TN_DONT, TN_ECHO);
2447                         } else {
2448                             EchoOff();
2449                             TelnetRequest(TN_DO, TN_ECHO);
2450                             remoteEchoOption = TRUE;
2451                         }
2452                         break;
2453                       default:
2454                         if (appData.debugMode)
2455                           fprintf(debugFP, "%d ", option);
2456                         /* Whatever this is, we don't want it. */
2457                         TelnetRequest(TN_DONT, option);
2458                         break;
2459                     }
2460                     break;
2461                   case TN_WONT:
2462                     if (appData.debugMode)
2463                       fprintf(debugFP, "\n<WONT ");
2464                     switch (option = (unsigned char) buf[++i]) {
2465                       case TN_ECHO:
2466                         if (appData.debugMode)
2467                           fprintf(debugFP, "ECHO ");
2468                         /* Reply only if this is a change, according
2469                            to the protocol rules. */
2470                         if (!remoteEchoOption) break;
2471                         EchoOn();
2472                         TelnetRequest(TN_DONT, TN_ECHO);
2473                         remoteEchoOption = FALSE;
2474                         break;
2475                       default:
2476                         if (appData.debugMode)
2477                           fprintf(debugFP, "%d ", (unsigned char) option);
2478                         /* Whatever this is, it must already be turned
2479                            off, because we never agree to turn on
2480                            anything non-default, so according to the
2481                            protocol rules, we don't reply. */
2482                         break;
2483                     }
2484                     break;
2485                   case TN_DO:
2486                     if (appData.debugMode)
2487                       fprintf(debugFP, "\n<DO ");
2488                     switch (option = (unsigned char) buf[++i]) {
2489                       default:
2490                         /* Whatever this is, we refuse to do it. */
2491                         if (appData.debugMode)
2492                           fprintf(debugFP, "%d ", option);
2493                         TelnetRequest(TN_WONT, option);
2494                         break;
2495                     }
2496                     break;
2497                   case TN_DONT:
2498                     if (appData.debugMode)
2499                       fprintf(debugFP, "\n<DONT ");
2500                     switch (option = (unsigned char) buf[++i]) {
2501                       default:
2502                         if (appData.debugMode)
2503                           fprintf(debugFP, "%d ", option);
2504                         /* Whatever this is, we are already not doing
2505                            it, because we never agree to do anything
2506                            non-default, so according to the protocol
2507                            rules, we don't reply. */
2508                         break;
2509                     }
2510                     break;
2511                   case TN_IAC:
2512                     if (appData.debugMode)
2513                       fprintf(debugFP, "\n<IAC ");
2514                     /* Doubled IAC; pass it through */
2515                     i--;
2516                     break;
2517                   default:
2518                     if (appData.debugMode)
2519                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2520                     /* Drop all other telnet commands on the floor */
2521                     break;
2522                 }
2523                 if (oldi > next_out)
2524                   SendToPlayer(&buf[next_out], oldi - next_out);
2525                 if (++i > next_out)
2526                   next_out = i;
2527                 continue;
2528             }
2529                 
2530             /* OK, this at least will *usually* work */
2531             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2532                 loggedOn = TRUE;
2533             }
2534             
2535             if (loggedOn && !intfSet) {
2536                 if (ics_type == ICS_ICC) {
2537                   sprintf(str,
2538                           "/set-quietly interface %s\n/set-quietly style 12\n",
2539                           programVersion);
2540                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2541                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2542                 } else if (ics_type == ICS_CHESSNET) {
2543                   sprintf(str, "/style 12\n");
2544                 } else {
2545                   strcpy(str, "alias $ @\n$set interface ");
2546                   strcat(str, programVersion);
2547                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2548                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2549                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2550 #ifdef WIN32
2551                   strcat(str, "$iset nohighlight 1\n");
2552 #endif
2553                   strcat(str, "$iset lock 1\n$style 12\n");
2554                 }
2555                 SendToICS(str);
2556                 NotifyFrontendLogin();
2557                 intfSet = TRUE;
2558             }
2559
2560             if (started == STARTED_COMMENT) {
2561                 /* Accumulate characters in comment */
2562                 parse[parse_pos++] = buf[i];
2563                 if (buf[i] == '\n') {
2564                     parse[parse_pos] = NULLCHAR;
2565                     if(chattingPartner>=0) {
2566                         char mess[MSG_SIZ];
2567                         sprintf(mess, "%s%s", talker, parse);
2568                         OutputChatMessage(chattingPartner, mess);
2569                         chattingPartner = -1;
2570                         next_out = i+1; // [HGM] suppress printing in ICS window
2571                     } else
2572                     if(!suppressKibitz) // [HGM] kibitz
2573                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2574                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2575                         int nrDigit = 0, nrAlph = 0, j;
2576                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2577                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2578                         parse[parse_pos] = NULLCHAR;
2579                         // try to be smart: if it does not look like search info, it should go to
2580                         // ICS interaction window after all, not to engine-output window.
2581                         for(j=0; j<parse_pos; j++) { // count letters and digits
2582                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2583                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2584                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2585                         }
2586                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2587                             int depth=0; float score;
2588                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2589                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2590                                 pvInfoList[forwardMostMove-1].depth = depth;
2591                                 pvInfoList[forwardMostMove-1].score = 100*score;
2592                             }
2593                             OutputKibitz(suppressKibitz, parse);
2594                         } else {
2595                             char tmp[MSG_SIZ];
2596                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2597                             SendToPlayer(tmp, strlen(tmp));
2598                         }
2599                         next_out = i+1; // [HGM] suppress printing in ICS window
2600                     }
2601                     started = STARTED_NONE;
2602                 } else {
2603                     /* Don't match patterns against characters in comment */
2604                     i++;
2605                     continue;
2606                 }
2607             }
2608             if (started == STARTED_CHATTER) {
2609                 if (buf[i] != '\n') {
2610                     /* Don't match patterns against characters in chatter */
2611                     i++;
2612                     continue;
2613                 }
2614                 started = STARTED_NONE;
2615                 if(suppressKibitz) next_out = i+1;
2616             }
2617
2618             /* Kludge to deal with rcmd protocol */
2619             if (firstTime && looking_at(buf, &i, "\001*")) {
2620                 DisplayFatalError(&buf[1], 0, 1);
2621                 continue;
2622             } else {
2623                 firstTime = FALSE;
2624             }
2625
2626             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2627                 ics_type = ICS_ICC;
2628                 ics_prefix = "/";
2629                 if (appData.debugMode)
2630                   fprintf(debugFP, "ics_type %d\n", ics_type);
2631                 continue;
2632             }
2633             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2634                 ics_type = ICS_FICS;
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, "chess.net")) {
2641                 ics_type = ICS_CHESSNET;
2642                 ics_prefix = "/";
2643                 if (appData.debugMode)
2644                   fprintf(debugFP, "ics_type %d\n", ics_type);
2645                 continue;
2646             }
2647
2648             if (!loggedOn &&
2649                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2650                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2651                  looking_at(buf, &i, "will be \"*\""))) {
2652               strcpy(ics_handle, star_match[0]);
2653               continue;
2654             }
2655
2656             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2657               char buf[MSG_SIZ];
2658               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2659               DisplayIcsInteractionTitle(buf);
2660               have_set_title = TRUE;
2661             }
2662
2663             /* skip finger notes */
2664             if (started == STARTED_NONE &&
2665                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2666                  (buf[i] == '1' && buf[i+1] == '0')) &&
2667                 buf[i+2] == ':' && buf[i+3] == ' ') {
2668               started = STARTED_CHATTER;
2669               i += 3;
2670               continue;
2671             }
2672
2673             oldi = i;
2674             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2675             if(appData.seekGraph) {
2676                 if(soughtPending && MatchSoughtLine(buf+i)) {
2677                     i = strstr(buf+i, "rated") - buf;
2678                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2679                     next_out = leftover_start = i;
2680                     started = STARTED_CHATTER;
2681                     suppressKibitz = TRUE;
2682                     continue;
2683                 }
2684                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2685                         && looking_at(buf, &i, "* ads displayed")) {
2686                     soughtPending = FALSE;
2687                     seekGraphUp = TRUE;
2688                     DrawSeekGraph();
2689                     continue;
2690                 }
2691                 if(appData.autoRefresh) {
2692                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2693                         int s = (ics_type == ICS_ICC); // ICC format differs
2694                         if(seekGraphUp)
2695                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]), 
2696                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2697                         looking_at(buf, &i, "*% "); // eat prompt
2698                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2699                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2700                         next_out = i; // suppress
2701                         continue;
2702                     }
2703                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2704                         char *p = star_match[0];
2705                         while(*p) {
2706                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2707                             while(*p && *p++ != ' '); // next
2708                         }
2709                         looking_at(buf, &i, "*% "); // eat prompt
2710                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2711                         next_out = i;
2712                         continue;
2713                     }
2714                 }
2715             }
2716
2717             /* skip formula vars */
2718             if (started == STARTED_NONE &&
2719                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2720               started = STARTED_CHATTER;
2721               i += 3;
2722               continue;
2723             }
2724
2725             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2726             if (appData.autoKibitz && started == STARTED_NONE && 
2727                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2728                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2729                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2730                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2731                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2732                         suppressKibitz = TRUE;
2733                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2734                         next_out = i;
2735                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2736                                 && (gameMode == IcsPlayingWhite)) ||
2737                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2738                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2739                             started = STARTED_CHATTER; // own kibitz we simply discard
2740                         else {
2741                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2742                             parse_pos = 0; parse[0] = NULLCHAR;
2743                             savingComment = TRUE;
2744                             suppressKibitz = gameMode != IcsObserving ? 2 :
2745                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2746                         } 
2747                         continue;
2748                 } else
2749                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2750                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2751                          && atoi(star_match[0])) {
2752                     // suppress the acknowledgements of our own autoKibitz
2753                     char *p;
2754                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2755                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2756                     SendToPlayer(star_match[0], strlen(star_match[0]));
2757                     if(looking_at(buf, &i, "*% ")) // eat prompt
2758                         suppressKibitz = FALSE;
2759                     next_out = i;
2760                     continue;
2761                 }
2762             } // [HGM] kibitz: end of patch
2763
2764             // [HGM] chat: intercept tells by users for which we have an open chat window
2765             channel = -1;
2766             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2767                                            looking_at(buf, &i, "* whispers:") ||
2768                                            looking_at(buf, &i, "* kibitzes:") ||
2769                                            looking_at(buf, &i, "* shouts:") ||
2770                                            looking_at(buf, &i, "* c-shouts:") ||
2771                                            looking_at(buf, &i, "--> * ") ||
2772                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2773                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2774                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2775                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2776                 int p;
2777                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2778                 chattingPartner = -1;
2779
2780                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2781                 for(p=0; p<MAX_CHAT; p++) {
2782                     if(channel == atoi(chatPartner[p])) {
2783                     talker[0] = '['; strcat(talker, "] ");
2784                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2785                     chattingPartner = p; break;
2786                     }
2787                 } else
2788                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2789                 for(p=0; p<MAX_CHAT; p++) {
2790                     if(!strcmp("kibitzes", chatPartner[p])) {
2791                         talker[0] = '['; strcat(talker, "] ");
2792                         chattingPartner = p; break;
2793                     }
2794                 } else
2795                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2796                 for(p=0; p<MAX_CHAT; p++) {
2797                     if(!strcmp("whispers", chatPartner[p])) {
2798                         talker[0] = '['; strcat(talker, "] ");
2799                         chattingPartner = p; break;
2800                     }
2801                 } else
2802                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2803                   if(buf[i-8] == '-' && buf[i-3] == 't')
2804                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2805                     if(!strcmp("c-shouts", chatPartner[p])) {
2806                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2807                         chattingPartner = p; break;
2808                     }
2809                   }
2810                   if(chattingPartner < 0)
2811                   for(p=0; p<MAX_CHAT; p++) {
2812                     if(!strcmp("shouts", chatPartner[p])) {
2813                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2814                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2815                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2816                         chattingPartner = p; break;
2817                     }
2818                   }
2819                 }
2820                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2821                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2822                     talker[0] = 0; Colorize(ColorTell, FALSE);
2823                     chattingPartner = p; break;
2824                 }
2825                 if(chattingPartner<0) i = oldi; else {
2826                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2827                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2828                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2829                     started = STARTED_COMMENT;
2830                     parse_pos = 0; parse[0] = NULLCHAR;
2831                     savingComment = 3 + chattingPartner; // counts as TRUE
2832                     suppressKibitz = TRUE;
2833                     continue;
2834                 }
2835             } // [HGM] chat: end of patch
2836
2837             if (appData.zippyTalk || appData.zippyPlay) {
2838                 /* [DM] Backup address for color zippy lines */
2839                 backup = i;
2840 #if ZIPPY
2841                if (loggedOn == TRUE)
2842                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2843                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2844 #endif
2845             } // [DM] 'else { ' deleted
2846                 if (
2847                     /* Regular tells and says */
2848                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2849                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2850                     looking_at(buf, &i, "* says: ") ||
2851                     /* Don't color "message" or "messages" output */
2852                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2853                     looking_at(buf, &i, "*. * at *:*: ") ||
2854                     looking_at(buf, &i, "--* (*:*): ") ||
2855                     /* Message notifications (same color as tells) */
2856                     looking_at(buf, &i, "* has left a message ") ||
2857                     looking_at(buf, &i, "* just sent you a message:\n") ||
2858                     /* Whispers and kibitzes */
2859                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2860                     looking_at(buf, &i, "* kibitzes: ") ||
2861                     /* Channel tells */
2862                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2863
2864                   if (tkind == 1 && strchr(star_match[0], ':')) {
2865                       /* Avoid "tells you:" spoofs in channels */
2866                      tkind = 3;
2867                   }
2868                   if (star_match[0][0] == NULLCHAR ||
2869                       strchr(star_match[0], ' ') ||
2870                       (tkind == 3 && strchr(star_match[1], ' '))) {
2871                     /* Reject bogus matches */
2872                     i = oldi;
2873                   } else {
2874                     if (appData.colorize) {
2875                       if (oldi > next_out) {
2876                         SendToPlayer(&buf[next_out], oldi - next_out);
2877                         next_out = oldi;
2878                       }
2879                       switch (tkind) {
2880                       case 1:
2881                         Colorize(ColorTell, FALSE);
2882                         curColor = ColorTell;
2883                         break;
2884                       case 2:
2885                         Colorize(ColorKibitz, FALSE);
2886                         curColor = ColorKibitz;
2887                         break;
2888                       case 3:
2889                         p = strrchr(star_match[1], '(');
2890                         if (p == NULL) {
2891                           p = star_match[1];
2892                         } else {
2893                           p++;
2894                         }
2895                         if (atoi(p) == 1) {
2896                           Colorize(ColorChannel1, FALSE);
2897                           curColor = ColorChannel1;
2898                         } else {
2899                           Colorize(ColorChannel, FALSE);
2900                           curColor = ColorChannel;
2901                         }
2902                         break;
2903                       case 5:
2904                         curColor = ColorNormal;
2905                         break;
2906                       }
2907                     }
2908                     if (started == STARTED_NONE && appData.autoComment &&
2909                         (gameMode == IcsObserving ||
2910                          gameMode == IcsPlayingWhite ||
2911                          gameMode == IcsPlayingBlack)) {
2912                       parse_pos = i - oldi;
2913                       memcpy(parse, &buf[oldi], parse_pos);
2914                       parse[parse_pos] = NULLCHAR;
2915                       started = STARTED_COMMENT;
2916                       savingComment = TRUE;
2917                     } else {
2918                       started = STARTED_CHATTER;
2919                       savingComment = FALSE;
2920                     }
2921                     loggedOn = TRUE;
2922                     continue;
2923                   }
2924                 }
2925
2926                 if (looking_at(buf, &i, "* s-shouts: ") ||
2927                     looking_at(buf, &i, "* c-shouts: ")) {
2928                     if (appData.colorize) {
2929                         if (oldi > next_out) {
2930                             SendToPlayer(&buf[next_out], oldi - next_out);
2931                             next_out = oldi;
2932                         }
2933                         Colorize(ColorSShout, FALSE);
2934                         curColor = ColorSShout;
2935                     }
2936                     loggedOn = TRUE;
2937                     started = STARTED_CHATTER;
2938                     continue;
2939                 }
2940
2941                 if (looking_at(buf, &i, "--->")) {
2942                     loggedOn = TRUE;
2943                     continue;
2944                 }
2945
2946                 if (looking_at(buf, &i, "* shouts: ") ||
2947                     looking_at(buf, &i, "--> ")) {
2948                     if (appData.colorize) {
2949                         if (oldi > next_out) {
2950                             SendToPlayer(&buf[next_out], oldi - next_out);
2951                             next_out = oldi;
2952                         }
2953                         Colorize(ColorShout, FALSE);
2954                         curColor = ColorShout;
2955                     }
2956                     loggedOn = TRUE;
2957                     started = STARTED_CHATTER;
2958                     continue;
2959                 }
2960
2961                 if (looking_at( buf, &i, "Challenge:")) {
2962                     if (appData.colorize) {
2963                         if (oldi > next_out) {
2964                             SendToPlayer(&buf[next_out], oldi - next_out);
2965                             next_out = oldi;
2966                         }
2967                         Colorize(ColorChallenge, FALSE);
2968                         curColor = ColorChallenge;
2969                     }
2970                     loggedOn = TRUE;
2971                     continue;
2972                 }
2973
2974                 if (looking_at(buf, &i, "* offers you") ||
2975                     looking_at(buf, &i, "* offers to be") ||
2976                     looking_at(buf, &i, "* would like to") ||
2977                     looking_at(buf, &i, "* requests to") ||
2978                     looking_at(buf, &i, "Your opponent offers") ||
2979                     looking_at(buf, &i, "Your opponent requests")) {
2980
2981                     if (appData.colorize) {
2982                         if (oldi > next_out) {
2983                             SendToPlayer(&buf[next_out], oldi - next_out);
2984                             next_out = oldi;
2985                         }
2986                         Colorize(ColorRequest, FALSE);
2987                         curColor = ColorRequest;
2988                     }
2989                     continue;
2990                 }
2991
2992                 if (looking_at(buf, &i, "* (*) seeking")) {
2993                     if (appData.colorize) {
2994                         if (oldi > next_out) {
2995                             SendToPlayer(&buf[next_out], oldi - next_out);
2996                             next_out = oldi;
2997                         }
2998                         Colorize(ColorSeek, FALSE);
2999                         curColor = ColorSeek;
3000                     }
3001                     continue;
3002             }
3003
3004             if (looking_at(buf, &i, "\\   ")) {
3005                 if (prevColor != ColorNormal) {
3006                     if (oldi > next_out) {
3007                         SendToPlayer(&buf[next_out], oldi - next_out);
3008                         next_out = oldi;
3009                     }
3010                     Colorize(prevColor, TRUE);
3011                     curColor = prevColor;
3012                 }
3013                 if (savingComment) {
3014                     parse_pos = i - oldi;
3015                     memcpy(parse, &buf[oldi], parse_pos);
3016                     parse[parse_pos] = NULLCHAR;
3017                     started = STARTED_COMMENT;
3018                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3019                         chattingPartner = savingComment - 3; // kludge to remember the box
3020                 } else {
3021                     started = STARTED_CHATTER;
3022                 }
3023                 continue;
3024             }
3025
3026             if (looking_at(buf, &i, "Black Strength :") ||
3027                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3028                 looking_at(buf, &i, "<10>") ||
3029                 looking_at(buf, &i, "#@#")) {
3030                 /* Wrong board style */
3031                 loggedOn = TRUE;
3032                 SendToICS(ics_prefix);
3033                 SendToICS("set style 12\n");
3034                 SendToICS(ics_prefix);
3035                 SendToICS("refresh\n");
3036                 continue;
3037             }
3038             
3039             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3040                 ICSInitScript();
3041                 have_sent_ICS_logon = 1;
3042                 continue;
3043             }
3044               
3045             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
3046                 (looking_at(buf, &i, "\n<12> ") ||
3047                  looking_at(buf, &i, "<12> "))) {
3048                 loggedOn = TRUE;
3049                 if (oldi > next_out) {
3050                     SendToPlayer(&buf[next_out], oldi - next_out);
3051                 }
3052                 next_out = i;
3053                 started = STARTED_BOARD;
3054                 parse_pos = 0;
3055                 continue;
3056             }
3057
3058             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3059                 looking_at(buf, &i, "<b1> ")) {
3060                 if (oldi > next_out) {
3061                     SendToPlayer(&buf[next_out], oldi - next_out);
3062                 }
3063                 next_out = i;
3064                 started = STARTED_HOLDINGS;
3065                 parse_pos = 0;
3066                 continue;
3067             }
3068
3069             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3070                 loggedOn = TRUE;
3071                 /* Header for a move list -- first line */
3072
3073                 switch (ics_getting_history) {
3074                   case H_FALSE:
3075                     switch (gameMode) {
3076                       case IcsIdle:
3077                       case BeginningOfGame:
3078                         /* User typed "moves" or "oldmoves" while we
3079                            were idle.  Pretend we asked for these
3080                            moves and soak them up so user can step
3081                            through them and/or save them.
3082                            */
3083                         Reset(FALSE, TRUE);
3084                         gameMode = IcsObserving;
3085                         ModeHighlight();
3086                         ics_gamenum = -1;
3087                         ics_getting_history = H_GOT_UNREQ_HEADER;
3088                         break;
3089                       case EditGame: /*?*/
3090                       case EditPosition: /*?*/
3091                         /* Should above feature work in these modes too? */
3092                         /* For now it doesn't */
3093                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3094                         break;
3095                       default:
3096                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3097                         break;
3098                     }
3099                     break;
3100                   case H_REQUESTED:
3101                     /* Is this the right one? */
3102                     if (gameInfo.white && gameInfo.black &&
3103                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3104                         strcmp(gameInfo.black, star_match[2]) == 0) {
3105                         /* All is well */
3106                         ics_getting_history = H_GOT_REQ_HEADER;
3107                     }
3108                     break;
3109                   case H_GOT_REQ_HEADER:
3110                   case H_GOT_UNREQ_HEADER:
3111                   case H_GOT_UNWANTED_HEADER:
3112                   case H_GETTING_MOVES:
3113                     /* Should not happen */
3114                     DisplayError(_("Error gathering move list: two headers"), 0);
3115                     ics_getting_history = H_FALSE;
3116                     break;
3117                 }
3118
3119                 /* Save player ratings into gameInfo if needed */
3120                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3121                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3122                     (gameInfo.whiteRating == -1 ||
3123                      gameInfo.blackRating == -1)) {
3124
3125                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3126                     gameInfo.blackRating = string_to_rating(star_match[3]);
3127                     if (appData.debugMode)
3128                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
3129                               gameInfo.whiteRating, gameInfo.blackRating);
3130                 }
3131                 continue;
3132             }
3133
3134             if (looking_at(buf, &i,
3135               "* * match, initial time: * minute*, increment: * second")) {
3136                 /* Header for a move list -- second line */
3137                 /* Initial board will follow if this is a wild game */
3138                 if (gameInfo.event != NULL) free(gameInfo.event);
3139                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3140                 gameInfo.event = StrSave(str);
3141                 /* [HGM] we switched variant. Translate boards if needed. */
3142                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3143                 continue;
3144             }
3145
3146             if (looking_at(buf, &i, "Move  ")) {
3147                 /* Beginning of a move list */
3148                 switch (ics_getting_history) {
3149                   case H_FALSE:
3150                     /* Normally should not happen */
3151                     /* Maybe user hit reset while we were parsing */
3152                     break;
3153                   case H_REQUESTED:
3154                     /* Happens if we are ignoring a move list that is not
3155                      * the one we just requested.  Common if the user
3156                      * tries to observe two games without turning off
3157                      * getMoveList */
3158                     break;
3159                   case H_GETTING_MOVES:
3160                     /* Should not happen */
3161                     DisplayError(_("Error gathering move list: nested"), 0);
3162                     ics_getting_history = H_FALSE;
3163                     break;
3164                   case H_GOT_REQ_HEADER:
3165                     ics_getting_history = H_GETTING_MOVES;
3166                     started = STARTED_MOVES;
3167                     parse_pos = 0;
3168                     if (oldi > next_out) {
3169                         SendToPlayer(&buf[next_out], oldi - next_out);
3170                     }
3171                     break;
3172                   case H_GOT_UNREQ_HEADER:
3173                     ics_getting_history = H_GETTING_MOVES;
3174                     started = STARTED_MOVES_NOHIDE;
3175                     parse_pos = 0;
3176                     break;
3177                   case H_GOT_UNWANTED_HEADER:
3178                     ics_getting_history = H_FALSE;
3179                     break;
3180                 }
3181                 continue;
3182             }                           
3183             
3184             if (looking_at(buf, &i, "% ") ||
3185                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3186                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3187                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3188                     soughtPending = FALSE;
3189                     seekGraphUp = TRUE;
3190                     DrawSeekGraph();
3191                 }
3192                 if(suppressKibitz) next_out = i;
3193                 savingComment = FALSE;
3194                 suppressKibitz = 0;
3195                 switch (started) {
3196                   case STARTED_MOVES:
3197                   case STARTED_MOVES_NOHIDE:
3198                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3199                     parse[parse_pos + i - oldi] = NULLCHAR;
3200                     ParseGameHistory(parse);
3201 #if ZIPPY
3202                     if (appData.zippyPlay && first.initDone) {
3203                         FeedMovesToProgram(&first, forwardMostMove);
3204                         if (gameMode == IcsPlayingWhite) {
3205                             if (WhiteOnMove(forwardMostMove)) {
3206                                 if (first.sendTime) {
3207                                   if (first.useColors) {
3208                                     SendToProgram("black\n", &first); 
3209                                   }
3210                                   SendTimeRemaining(&first, TRUE);
3211                                 }
3212                                 if (first.useColors) {
3213                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3214                                 }
3215                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3216                                 first.maybeThinking = TRUE;
3217                             } else {
3218                                 if (first.usePlayother) {
3219                                   if (first.sendTime) {
3220                                     SendTimeRemaining(&first, TRUE);
3221                                   }
3222                                   SendToProgram("playother\n", &first);
3223                                   firstMove = FALSE;
3224                                 } else {
3225                                   firstMove = TRUE;
3226                                 }
3227                             }
3228                         } else if (gameMode == IcsPlayingBlack) {
3229                             if (!WhiteOnMove(forwardMostMove)) {
3230                                 if (first.sendTime) {
3231                                   if (first.useColors) {
3232                                     SendToProgram("white\n", &first);
3233                                   }
3234                                   SendTimeRemaining(&first, FALSE);
3235                                 }
3236                                 if (first.useColors) {
3237                                   SendToProgram("black\n", &first);
3238                                 }
3239                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3240                                 first.maybeThinking = TRUE;
3241                             } else {
3242                                 if (first.usePlayother) {
3243                                   if (first.sendTime) {
3244                                     SendTimeRemaining(&first, FALSE);
3245                                   }
3246                                   SendToProgram("playother\n", &first);
3247                                   firstMove = FALSE;
3248                                 } else {
3249                                   firstMove = TRUE;
3250                                 }
3251                             }
3252                         }                       
3253                     }
3254 #endif
3255                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3256                         /* Moves came from oldmoves or moves command
3257                            while we weren't doing anything else.
3258                            */
3259                         currentMove = forwardMostMove;
3260                         ClearHighlights();/*!!could figure this out*/
3261                         flipView = appData.flipView;
3262                         DrawPosition(TRUE, boards[currentMove]);
3263                         DisplayBothClocks();
3264                         sprintf(str, "%s vs. %s",
3265                                 gameInfo.white, gameInfo.black);
3266                         DisplayTitle(str);
3267                         gameMode = IcsIdle;
3268                     } else {
3269                         /* Moves were history of an active game */
3270                         if (gameInfo.resultDetails != NULL) {
3271                             free(gameInfo.resultDetails);
3272                             gameInfo.resultDetails = NULL;
3273                         }
3274                     }
3275                     HistorySet(parseList, backwardMostMove,
3276                                forwardMostMove, currentMove-1);
3277                     DisplayMove(currentMove - 1);
3278                     if (started == STARTED_MOVES) next_out = i;
3279                     started = STARTED_NONE;
3280                     ics_getting_history = H_FALSE;
3281                     break;
3282
3283                   case STARTED_OBSERVE:
3284                     started = STARTED_NONE;
3285                     SendToICS(ics_prefix);
3286                     SendToICS("refresh\n");
3287                     break;
3288
3289                   default:
3290                     break;
3291                 }
3292                 if(bookHit) { // [HGM] book: simulate book reply
3293                     static char bookMove[MSG_SIZ]; // a bit generous?
3294
3295                     programStats.nodes = programStats.depth = programStats.time = 
3296                     programStats.score = programStats.got_only_move = 0;
3297                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3298
3299                     strcpy(bookMove, "move ");
3300                     strcat(bookMove, bookHit);
3301                     HandleMachineMove(bookMove, &first);
3302                 }
3303                 continue;
3304             }
3305             
3306             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3307                  started == STARTED_HOLDINGS ||
3308                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3309                 /* Accumulate characters in move list or board */
3310                 parse[parse_pos++] = buf[i];
3311             }
3312             
3313             /* Start of game messages.  Mostly we detect start of game
3314                when the first board image arrives.  On some versions
3315                of the ICS, though, we need to do a "refresh" after starting
3316                to observe in order to get the current board right away. */
3317             if (looking_at(buf, &i, "Adding game * to observation list")) {
3318                 started = STARTED_OBSERVE;
3319                 continue;
3320             }
3321
3322             /* Handle auto-observe */
3323             if (appData.autoObserve &&
3324                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3325                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3326                 char *player;
3327                 /* Choose the player that was highlighted, if any. */
3328                 if (star_match[0][0] == '\033' ||
3329                     star_match[1][0] != '\033') {
3330                     player = star_match[0];
3331                 } else {
3332                     player = star_match[2];
3333                 }
3334                 sprintf(str, "%sobserve %s\n",
3335                         ics_prefix, StripHighlightAndTitle(player));
3336                 SendToICS(str);
3337
3338                 /* Save ratings from notify string */
3339                 strcpy(player1Name, star_match[0]);
3340                 player1Rating = string_to_rating(star_match[1]);
3341                 strcpy(player2Name, star_match[2]);
3342                 player2Rating = string_to_rating(star_match[3]);
3343
3344                 if (appData.debugMode)
3345                   fprintf(debugFP, 
3346                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3347                           player1Name, player1Rating,
3348                           player2Name, player2Rating);
3349
3350                 continue;
3351             }
3352
3353             /* Deal with automatic examine mode after a game,
3354                and with IcsObserving -> IcsExamining transition */
3355             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3356                 looking_at(buf, &i, "has made you an examiner of game *")) {
3357
3358                 int gamenum = atoi(star_match[0]);
3359                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3360                     gamenum == ics_gamenum) {
3361                     /* We were already playing or observing this game;
3362                        no need to refetch history */
3363                     gameMode = IcsExamining;
3364                     if (pausing) {
3365                         pauseExamForwardMostMove = forwardMostMove;
3366                     } else if (currentMove < forwardMostMove) {
3367                         ForwardInner(forwardMostMove);
3368                     }
3369                 } else {
3370                     /* I don't think this case really can happen */
3371                     SendToICS(ics_prefix);
3372                     SendToICS("refresh\n");
3373                 }
3374                 continue;
3375             }    
3376             
3377             /* Error messages */
3378 //          if (ics_user_moved) {
3379             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3380                 if (looking_at(buf, &i, "Illegal move") ||
3381                     looking_at(buf, &i, "Not a legal move") ||
3382                     looking_at(buf, &i, "Your king is in check") ||
3383                     looking_at(buf, &i, "It isn't your turn") ||
3384                     looking_at(buf, &i, "It is not your move")) {
3385                     /* Illegal move */
3386                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3387                         currentMove = forwardMostMove-1;
3388                         DisplayMove(currentMove - 1); /* before DMError */
3389                         DrawPosition(FALSE, boards[currentMove]);
3390                         SwitchClocks(forwardMostMove-1); // [HGM] race
3391                         DisplayBothClocks();
3392                     }
3393                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3394                     ics_user_moved = 0;
3395                     continue;
3396                 }
3397             }
3398
3399             if (looking_at(buf, &i, "still have time") ||
3400                 looking_at(buf, &i, "not out of time") ||
3401                 looking_at(buf, &i, "either player is out of time") ||
3402                 looking_at(buf, &i, "has timeseal; checking")) {
3403                 /* We must have called his flag a little too soon */
3404                 whiteFlag = blackFlag = FALSE;
3405                 continue;
3406             }
3407
3408             if (looking_at(buf, &i, "added * seconds to") ||
3409                 looking_at(buf, &i, "seconds were added to")) {
3410                 /* Update the clocks */
3411                 SendToICS(ics_prefix);
3412                 SendToICS("refresh\n");
3413                 continue;
3414             }
3415
3416             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3417                 ics_clock_paused = TRUE;
3418                 StopClocks();
3419                 continue;
3420             }
3421
3422             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3423                 ics_clock_paused = FALSE;
3424                 StartClocks();
3425                 continue;
3426             }
3427
3428             /* Grab player ratings from the Creating: message.
3429                Note we have to check for the special case when
3430                the ICS inserts things like [white] or [black]. */
3431             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3432                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3433                 /* star_matches:
3434                    0    player 1 name (not necessarily white)
3435                    1    player 1 rating
3436                    2    empty, white, or black (IGNORED)
3437                    3    player 2 name (not necessarily black)
3438                    4    player 2 rating
3439                    
3440                    The names/ratings are sorted out when the game
3441                    actually starts (below).
3442                 */
3443                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3444                 player1Rating = string_to_rating(star_match[1]);
3445                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3446                 player2Rating = string_to_rating(star_match[4]);
3447
3448                 if (appData.debugMode)
3449                   fprintf(debugFP, 
3450                           "Ratings from 'Creating:' %s %d, %s %d\n",
3451                           player1Name, player1Rating,
3452                           player2Name, player2Rating);
3453
3454                 continue;
3455             }
3456             
3457             /* Improved generic start/end-of-game messages */
3458             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3459                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3460                 /* If tkind == 0: */
3461                 /* star_match[0] is the game number */
3462                 /*           [1] is the white player's name */
3463                 /*           [2] is the black player's name */
3464                 /* For end-of-game: */
3465                 /*           [3] is the reason for the game end */
3466                 /*           [4] is a PGN end game-token, preceded by " " */
3467                 /* For start-of-game: */
3468                 /*           [3] begins with "Creating" or "Continuing" */
3469                 /*           [4] is " *" or empty (don't care). */
3470                 int gamenum = atoi(star_match[0]);
3471                 char *whitename, *blackname, *why, *endtoken;
3472                 ChessMove endtype = (ChessMove) 0;
3473
3474                 if (tkind == 0) {
3475                   whitename = star_match[1];
3476                   blackname = star_match[2];
3477                   why = star_match[3];
3478                   endtoken = star_match[4];
3479                 } else {
3480                   whitename = star_match[1];
3481                   blackname = star_match[3];
3482                   why = star_match[5];
3483                   endtoken = star_match[6];
3484                 }
3485
3486                 /* Game start messages */
3487                 if (strncmp(why, "Creating ", 9) == 0 ||
3488                     strncmp(why, "Continuing ", 11) == 0) {
3489                     gs_gamenum = gamenum;
3490                     strcpy(gs_kind, strchr(why, ' ') + 1);
3491                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3492 #if ZIPPY
3493                     if (appData.zippyPlay) {
3494                         ZippyGameStart(whitename, blackname);
3495                     }
3496 #endif /*ZIPPY*/
3497                     partnerBoardValid = FALSE; // [HGM] bughouse
3498                     continue;
3499                 }
3500
3501                 /* Game end messages */
3502                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3503                     ics_gamenum != gamenum) {
3504                     continue;
3505                 }
3506                 while (endtoken[0] == ' ') endtoken++;
3507                 switch (endtoken[0]) {
3508                   case '*':
3509                   default:
3510                     endtype = GameUnfinished;
3511                     break;
3512                   case '0':
3513                     endtype = BlackWins;
3514                     break;
3515                   case '1':
3516                     if (endtoken[1] == '/')
3517                       endtype = GameIsDrawn;
3518                     else
3519                       endtype = WhiteWins;
3520                     break;
3521                 }
3522                 GameEnds(endtype, why, GE_ICS);
3523 #if ZIPPY
3524                 if (appData.zippyPlay && first.initDone) {
3525                     ZippyGameEnd(endtype, why);
3526                     if (first.pr == NULL) {
3527                       /* Start the next process early so that we'll
3528                          be ready for the next challenge */
3529                       StartChessProgram(&first);
3530                     }
3531                     /* Send "new" early, in case this command takes
3532                        a long time to finish, so that we'll be ready
3533                        for the next challenge. */
3534                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3535                     Reset(TRUE, TRUE);
3536                 }
3537 #endif /*ZIPPY*/
3538                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3539                 continue;
3540             }
3541
3542             if (looking_at(buf, &i, "Removing game * from observation") ||
3543                 looking_at(buf, &i, "no longer observing game *") ||
3544                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3545                 if (gameMode == IcsObserving &&
3546                     atoi(star_match[0]) == ics_gamenum)
3547                   {
3548                       /* icsEngineAnalyze */
3549                       if (appData.icsEngineAnalyze) {
3550                             ExitAnalyzeMode();
3551                             ModeHighlight();
3552                       }
3553                       StopClocks();
3554                       gameMode = IcsIdle;
3555                       ics_gamenum = -1;
3556                       ics_user_moved = FALSE;
3557                   }
3558                 continue;
3559             }
3560
3561             if (looking_at(buf, &i, "no longer examining game *")) {
3562                 if (gameMode == IcsExamining &&
3563                     atoi(star_match[0]) == ics_gamenum)
3564                   {
3565                       gameMode = IcsIdle;
3566                       ics_gamenum = -1;
3567                       ics_user_moved = FALSE;
3568                   }
3569                 continue;
3570             }
3571
3572             /* Advance leftover_start past any newlines we find,
3573                so only partial lines can get reparsed */
3574             if (looking_at(buf, &i, "\n")) {
3575                 prevColor = curColor;
3576                 if (curColor != ColorNormal) {
3577                     if (oldi > next_out) {
3578                         SendToPlayer(&buf[next_out], oldi - next_out);
3579                         next_out = oldi;
3580                     }
3581                     Colorize(ColorNormal, FALSE);
3582                     curColor = ColorNormal;
3583                 }
3584                 if (started == STARTED_BOARD) {
3585                     started = STARTED_NONE;
3586                     parse[parse_pos] = NULLCHAR;
3587                     ParseBoard12(parse);
3588                     ics_user_moved = 0;
3589
3590                     /* Send premove here */
3591                     if (appData.premove) {
3592                       char str[MSG_SIZ];
3593                       if (currentMove == 0 &&
3594                           gameMode == IcsPlayingWhite &&
3595                           appData.premoveWhite) {
3596                         sprintf(str, "%s\n", appData.premoveWhiteText);
3597                         if (appData.debugMode)
3598                           fprintf(debugFP, "Sending premove:\n");
3599                         SendToICS(str);
3600                       } else if (currentMove == 1 &&
3601                                  gameMode == IcsPlayingBlack &&
3602                                  appData.premoveBlack) {
3603                         sprintf(str, "%s\n", appData.premoveBlackText);
3604                         if (appData.debugMode)
3605                           fprintf(debugFP, "Sending premove:\n");
3606                         SendToICS(str);
3607                       } else if (gotPremove) {
3608                         gotPremove = 0;
3609                         ClearPremoveHighlights();
3610                         if (appData.debugMode)
3611                           fprintf(debugFP, "Sending premove:\n");
3612                           UserMoveEvent(premoveFromX, premoveFromY, 
3613                                         premoveToX, premoveToY, 
3614                                         premovePromoChar);
3615                       }
3616                     }
3617
3618                     /* Usually suppress following prompt */
3619                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3620                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3621                         if (looking_at(buf, &i, "*% ")) {
3622                             savingComment = FALSE;
3623                             suppressKibitz = 0;
3624                         }
3625                     }
3626                     next_out = i;
3627                 } else if (started == STARTED_HOLDINGS) {
3628                     int gamenum;
3629                     char new_piece[MSG_SIZ];
3630                     started = STARTED_NONE;
3631                     parse[parse_pos] = NULLCHAR;
3632                     if (appData.debugMode)
3633                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3634                                                         parse, currentMove);
3635                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3636                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3637                         if (gameInfo.variant == VariantNormal) {
3638                           /* [HGM] We seem to switch variant during a game!
3639                            * Presumably no holdings were displayed, so we have
3640                            * to move the position two files to the right to
3641                            * create room for them!
3642                            */
3643                           VariantClass newVariant;
3644                           switch(gameInfo.boardWidth) { // base guess on board width
3645                                 case 9:  newVariant = VariantShogi; break;
3646                                 case 10: newVariant = VariantGreat; break;
3647                                 default: newVariant = VariantCrazyhouse; break;
3648                           }
3649                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3650                           /* Get a move list just to see the header, which
3651                              will tell us whether this is really bug or zh */
3652                           if (ics_getting_history == H_FALSE) {
3653                             ics_getting_history = H_REQUESTED;
3654                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3655                             SendToICS(str);
3656                           }
3657                         }
3658                         new_piece[0] = NULLCHAR;
3659                         sscanf(parse, "game %d white [%s black [%s <- %s",
3660                                &gamenum, white_holding, black_holding,
3661                                new_piece);
3662                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3663                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3664                         /* [HGM] copy holdings to board holdings area */
3665                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3666                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3667                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3668 #if ZIPPY
3669                         if (appData.zippyPlay && first.initDone) {
3670                             ZippyHoldings(white_holding, black_holding,
3671                                           new_piece);
3672                         }
3673 #endif /*ZIPPY*/
3674                         if (tinyLayout || smallLayout) {
3675                             char wh[16], bh[16];
3676                             PackHolding(wh, white_holding);
3677                             PackHolding(bh, black_holding);
3678                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3679                                     gameInfo.white, gameInfo.black);
3680                         } else {
3681                             sprintf(str, "%s [%s] vs. %s [%s]",
3682                                     gameInfo.white, white_holding,
3683                                     gameInfo.black, black_holding);
3684                         }
3685                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3686                         DrawPosition(FALSE, boards[currentMove]);
3687                         DisplayTitle(str);
3688                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3689                         sscanf(parse, "game %d white [%s black [%s <- %s",
3690                                &gamenum, white_holding, black_holding,
3691                                new_piece);
3692                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3693                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3694                         /* [HGM] copy holdings to partner-board holdings area */
3695                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3696                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3697                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3698                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3699                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3700                       }
3701                     }
3702                     /* Suppress following prompt */
3703                     if (looking_at(buf, &i, "*% ")) {
3704                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3705                         savingComment = FALSE;
3706                         suppressKibitz = 0;
3707                     }
3708                     next_out = i;
3709                 }
3710                 continue;
3711             }
3712
3713             i++;                /* skip unparsed character and loop back */
3714         }
3715         
3716         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3717 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3718 //          SendToPlayer(&buf[next_out], i - next_out);
3719             started != STARTED_HOLDINGS && leftover_start > next_out) {
3720             SendToPlayer(&buf[next_out], leftover_start - next_out);
3721             next_out = i;
3722         }
3723         
3724         leftover_len = buf_len - leftover_start;
3725         /* if buffer ends with something we couldn't parse,
3726            reparse it after appending the next read */
3727         
3728     } else if (count == 0) {
3729         RemoveInputSource(isr);
3730         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3731     } else {
3732         DisplayFatalError(_("Error reading from ICS"), error, 1);
3733     }
3734 }
3735
3736
3737 /* Board style 12 looks like this:
3738    
3739    <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
3740    
3741  * The "<12> " is stripped before it gets to this routine.  The two
3742  * trailing 0's (flip state and clock ticking) are later addition, and
3743  * some chess servers may not have them, or may have only the first.
3744  * Additional trailing fields may be added in the future.  
3745  */
3746
3747 #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"
3748
3749 #define RELATION_OBSERVING_PLAYED    0
3750 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3751 #define RELATION_PLAYING_MYMOVE      1
3752 #define RELATION_PLAYING_NOTMYMOVE  -1
3753 #define RELATION_EXAMINING           2
3754 #define RELATION_ISOLATED_BOARD     -3
3755 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3756
3757 void
3758 ParseBoard12(string)
3759      char *string;
3760
3761     GameMode newGameMode;
3762     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3763     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3764     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3765     char to_play, board_chars[200];
3766     char move_str[500], str[500], elapsed_time[500];
3767     char black[32], white[32];
3768     Board board;
3769     int prevMove = currentMove;
3770     int ticking = 2;
3771     ChessMove moveType;
3772     int fromX, fromY, toX, toY;
3773     char promoChar;
3774     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3775     char *bookHit = NULL; // [HGM] book
3776     Boolean weird = FALSE, reqFlag = FALSE;
3777
3778     fromX = fromY = toX = toY = -1;
3779     
3780     newGame = FALSE;
3781
3782     if (appData.debugMode)
3783       fprintf(debugFP, _("Parsing board: %s\n"), string);
3784
3785     move_str[0] = NULLCHAR;
3786     elapsed_time[0] = NULLCHAR;
3787     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3788         int  i = 0, j;
3789         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3790             if(string[i] == ' ') { ranks++; files = 0; }
3791             else files++;
3792             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3793             i++;
3794         }
3795         for(j = 0; j <i; j++) board_chars[j] = string[j];
3796         board_chars[i] = '\0';
3797         string += i + 1;
3798     }
3799     n = sscanf(string, PATTERN, &to_play, &double_push,
3800                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3801                &gamenum, white, black, &relation, &basetime, &increment,
3802                &white_stren, &black_stren, &white_time, &black_time,
3803                &moveNum, str, elapsed_time, move_str, &ics_flip,
3804                &ticking);
3805
3806     if (n < 21) {
3807         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3808         DisplayError(str, 0);
3809         return;
3810     }
3811
3812     /* Convert the move number to internal form */
3813     moveNum = (moveNum - 1) * 2;
3814     if (to_play == 'B') moveNum++;
3815     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3816       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3817                         0, 1);
3818       return;
3819     }
3820     
3821     switch (relation) {
3822       case RELATION_OBSERVING_PLAYED:
3823       case RELATION_OBSERVING_STATIC:
3824         if (gamenum == -1) {
3825             /* Old ICC buglet */
3826             relation = RELATION_OBSERVING_STATIC;
3827         }
3828         newGameMode = IcsObserving;
3829         break;
3830       case RELATION_PLAYING_MYMOVE:
3831       case RELATION_PLAYING_NOTMYMOVE:
3832         newGameMode =
3833           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3834             IcsPlayingWhite : IcsPlayingBlack;
3835         break;
3836       case RELATION_EXAMINING:
3837         newGameMode = IcsExamining;
3838         break;
3839       case RELATION_ISOLATED_BOARD:
3840       default:
3841         /* Just display this board.  If user was doing something else,
3842            we will forget about it until the next board comes. */ 
3843         newGameMode = IcsIdle;
3844         break;
3845       case RELATION_STARTING_POSITION:
3846         newGameMode = gameMode;
3847         break;
3848     }
3849     
3850     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3851          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3852       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3853       char *toSqr;
3854       for (k = 0; k < ranks; k++) {
3855         for (j = 0; j < files; j++)
3856           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3857         if(gameInfo.holdingsWidth > 1) {
3858              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3859              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3860         }
3861       }
3862       CopyBoard(partnerBoard, board);
3863       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3864         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3865         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3866       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3867       if(toSqr = strchr(str, '-')) {
3868         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3869         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3870       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3871       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3872       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3873       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3874       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3875       sprintf(partnerStatus, "W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3876                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3877       DisplayMessage(partnerStatus, "");
3878         partnerBoardValid = TRUE;
3879       return;
3880     }
3881
3882     /* Modify behavior for initial board display on move listing
3883        of wild games.
3884        */
3885     switch (ics_getting_history) {
3886       case H_FALSE:
3887       case H_REQUESTED:
3888         break;
3889       case H_GOT_REQ_HEADER:
3890       case H_GOT_UNREQ_HEADER:
3891         /* This is the initial position of the current game */
3892         gamenum = ics_gamenum;
3893         moveNum = 0;            /* old ICS bug workaround */
3894         if (to_play == 'B') {
3895           startedFromSetupPosition = TRUE;
3896           blackPlaysFirst = TRUE;
3897           moveNum = 1;
3898           if (forwardMostMove == 0) forwardMostMove = 1;
3899           if (backwardMostMove == 0) backwardMostMove = 1;
3900           if (currentMove == 0) currentMove = 1;
3901         }
3902         newGameMode = gameMode;
3903         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3904         break;
3905       case H_GOT_UNWANTED_HEADER:
3906         /* This is an initial board that we don't want */
3907         return;
3908       case H_GETTING_MOVES:
3909         /* Should not happen */
3910         DisplayError(_("Error gathering move list: extra board"), 0);
3911         ics_getting_history = H_FALSE;
3912         return;
3913     }
3914
3915    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3916                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3917      /* [HGM] We seem to have switched variant unexpectedly
3918       * Try to guess new variant from board size
3919       */
3920           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3921           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3922           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3923           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3924           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3925           if(!weird) newVariant = VariantNormal;
3926           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3927           /* Get a move list just to see the header, which
3928              will tell us whether this is really bug or zh */
3929           if (ics_getting_history == H_FALSE) {
3930             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3931             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3932             SendToICS(str);
3933           }
3934     }
3935     
3936     /* Take action if this is the first board of a new game, or of a
3937        different game than is currently being displayed.  */
3938     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3939         relation == RELATION_ISOLATED_BOARD) {
3940         
3941         /* Forget the old game and get the history (if any) of the new one */
3942         if (gameMode != BeginningOfGame) {
3943           Reset(TRUE, TRUE);
3944         }
3945         newGame = TRUE;
3946         if (appData.autoRaiseBoard) BoardToTop();
3947         prevMove = -3;
3948         if (gamenum == -1) {
3949             newGameMode = IcsIdle;
3950         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3951                    appData.getMoveList && !reqFlag) {
3952             /* Need to get game history */
3953             ics_getting_history = H_REQUESTED;
3954             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3955             SendToICS(str);
3956         }
3957         
3958         /* Initially flip the board to have black on the bottom if playing
3959            black or if the ICS flip flag is set, but let the user change
3960            it with the Flip View button. */
3961         flipView = appData.autoFlipView ? 
3962           (newGameMode == IcsPlayingBlack) || ics_flip :
3963           appData.flipView;
3964         
3965         /* Done with values from previous mode; copy in new ones */
3966         gameMode = newGameMode;
3967         ModeHighlight();
3968         ics_gamenum = gamenum;
3969         if (gamenum == gs_gamenum) {
3970             int klen = strlen(gs_kind);
3971             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3972             sprintf(str, "ICS %s", gs_kind);
3973             gameInfo.event = StrSave(str);
3974         } else {
3975             gameInfo.event = StrSave("ICS game");
3976         }
3977         gameInfo.site = StrSave(appData.icsHost);
3978         gameInfo.date = PGNDate();
3979         gameInfo.round = StrSave("-");
3980         gameInfo.white = StrSave(white);
3981         gameInfo.black = StrSave(black);
3982         timeControl = basetime * 60 * 1000;
3983         timeControl_2 = 0;
3984         timeIncrement = increment * 1000;
3985         movesPerSession = 0;
3986         gameInfo.timeControl = TimeControlTagValue();
3987         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3988   if (appData.debugMode) {
3989     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3990     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3991     setbuf(debugFP, NULL);
3992   }
3993
3994         gameInfo.outOfBook = NULL;
3995         
3996         /* Do we have the ratings? */
3997         if (strcmp(player1Name, white) == 0 &&
3998             strcmp(player2Name, black) == 0) {
3999             if (appData.debugMode)
4000               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4001                       player1Rating, player2Rating);
4002             gameInfo.whiteRating = player1Rating;
4003             gameInfo.blackRating = player2Rating;
4004         } else if (strcmp(player2Name, white) == 0 &&
4005                    strcmp(player1Name, black) == 0) {
4006             if (appData.debugMode)
4007               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4008                       player2Rating, player1Rating);
4009             gameInfo.whiteRating = player2Rating;
4010             gameInfo.blackRating = player1Rating;
4011         }
4012         player1Name[0] = player2Name[0] = NULLCHAR;
4013
4014         /* Silence shouts if requested */
4015         if (appData.quietPlay &&
4016             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4017             SendToICS(ics_prefix);
4018             SendToICS("set shout 0\n");
4019         }
4020     }
4021     
4022     /* Deal with midgame name changes */
4023     if (!newGame) {
4024         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4025             if (gameInfo.white) free(gameInfo.white);
4026             gameInfo.white = StrSave(white);
4027         }
4028         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4029             if (gameInfo.black) free(gameInfo.black);
4030             gameInfo.black = StrSave(black);
4031         }
4032     }
4033     
4034     /* Throw away game result if anything actually changes in examine mode */
4035     if (gameMode == IcsExamining && !newGame) {
4036         gameInfo.result = GameUnfinished;
4037         if (gameInfo.resultDetails != NULL) {
4038             free(gameInfo.resultDetails);
4039             gameInfo.resultDetails = NULL;
4040         }
4041     }
4042     
4043     /* In pausing && IcsExamining mode, we ignore boards coming
4044        in if they are in a different variation than we are. */
4045     if (pauseExamInvalid) return;
4046     if (pausing && gameMode == IcsExamining) {
4047         if (moveNum <= pauseExamForwardMostMove) {
4048             pauseExamInvalid = TRUE;
4049             forwardMostMove = pauseExamForwardMostMove;
4050             return;
4051         }
4052     }
4053     
4054   if (appData.debugMode) {
4055     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4056   }
4057     /* Parse the board */
4058     for (k = 0; k < ranks; k++) {
4059       for (j = 0; j < files; j++)
4060         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4061       if(gameInfo.holdingsWidth > 1) {
4062            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4063            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4064       }
4065     }
4066     CopyBoard(boards[moveNum], board);
4067     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4068     if (moveNum == 0) {
4069         startedFromSetupPosition =
4070           !CompareBoards(board, initialPosition);
4071         if(startedFromSetupPosition)
4072             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4073     }
4074
4075     /* [HGM] Set castling rights. Take the outermost Rooks,
4076        to make it also work for FRC opening positions. Note that board12
4077        is really defective for later FRC positions, as it has no way to
4078        indicate which Rook can castle if they are on the same side of King.
4079        For the initial position we grant rights to the outermost Rooks,
4080        and remember thos rights, and we then copy them on positions
4081        later in an FRC game. This means WB might not recognize castlings with
4082        Rooks that have moved back to their original position as illegal,
4083        but in ICS mode that is not its job anyway.
4084     */
4085     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4086     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4087
4088         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4089             if(board[0][i] == WhiteRook) j = i;
4090         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4091         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4092             if(board[0][i] == WhiteRook) j = i;
4093         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4094         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4095             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4096         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4097         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4098             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4099         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4100
4101         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4102         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4103             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4104         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4105             if(board[BOARD_HEIGHT-1][k] == bKing)
4106                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4107         if(gameInfo.variant == VariantTwoKings) {
4108             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4109             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4110             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4111         }
4112     } else { int r;
4113         r = boards[moveNum][CASTLING][0] = initialRights[0];
4114         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4115         r = boards[moveNum][CASTLING][1] = initialRights[1];
4116         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4117         r = boards[moveNum][CASTLING][3] = initialRights[3];
4118         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4119         r = boards[moveNum][CASTLING][4] = initialRights[4];
4120         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4121         /* wildcastle kludge: always assume King has rights */
4122         r = boards[moveNum][CASTLING][2] = initialRights[2];
4123         r = boards[moveNum][CASTLING][5] = initialRights[5];
4124     }
4125     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4126     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4127
4128     
4129     if (ics_getting_history == H_GOT_REQ_HEADER ||
4130         ics_getting_history == H_GOT_UNREQ_HEADER) {
4131         /* This was an initial position from a move list, not
4132            the current position */
4133         return;
4134     }
4135     
4136     /* Update currentMove and known move number limits */
4137     newMove = newGame || moveNum > forwardMostMove;
4138
4139     if (newGame) {
4140         forwardMostMove = backwardMostMove = currentMove = moveNum;
4141         if (gameMode == IcsExamining && moveNum == 0) {
4142           /* Workaround for ICS limitation: we are not told the wild
4143              type when starting to examine a game.  But if we ask for
4144              the move list, the move list header will tell us */
4145             ics_getting_history = H_REQUESTED;
4146             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4147             SendToICS(str);
4148         }
4149     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4150                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4151 #if ZIPPY
4152         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4153         /* [HGM] applied this also to an engine that is silently watching        */
4154         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4155             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4156             gameInfo.variant == currentlyInitializedVariant) {
4157           takeback = forwardMostMove - moveNum;
4158           for (i = 0; i < takeback; i++) {
4159             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4160             SendToProgram("undo\n", &first);
4161           }
4162         }
4163 #endif
4164
4165         forwardMostMove = moveNum;
4166         if (!pausing || currentMove > forwardMostMove)
4167           currentMove = forwardMostMove;
4168     } else {
4169         /* New part of history that is not contiguous with old part */ 
4170         if (pausing && gameMode == IcsExamining) {
4171             pauseExamInvalid = TRUE;
4172             forwardMostMove = pauseExamForwardMostMove;
4173             return;
4174         }
4175         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4176 #if ZIPPY
4177             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4178                 // [HGM] when we will receive the move list we now request, it will be
4179                 // fed to the engine from the first move on. So if the engine is not
4180                 // in the initial position now, bring it there.
4181                 InitChessProgram(&first, 0);
4182             }
4183 #endif
4184             ics_getting_history = H_REQUESTED;
4185             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4186             SendToICS(str);
4187         }
4188         forwardMostMove = backwardMostMove = currentMove = moveNum;
4189     }
4190     
4191     /* Update the clocks */
4192     if (strchr(elapsed_time, '.')) {
4193       /* Time is in ms */
4194       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4195       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4196     } else {
4197       /* Time is in seconds */
4198       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4199       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4200     }
4201       
4202
4203 #if ZIPPY
4204     if (appData.zippyPlay && newGame &&
4205         gameMode != IcsObserving && gameMode != IcsIdle &&
4206         gameMode != IcsExamining)
4207       ZippyFirstBoard(moveNum, basetime, increment);
4208 #endif
4209     
4210     /* Put the move on the move list, first converting
4211        to canonical algebraic form. */
4212     if (moveNum > 0) {
4213   if (appData.debugMode) {
4214     if (appData.debugMode) { int f = forwardMostMove;
4215         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4216                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4217                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4218     }
4219     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4220     fprintf(debugFP, "moveNum = %d\n", moveNum);
4221     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4222     setbuf(debugFP, NULL);
4223   }
4224         if (moveNum <= backwardMostMove) {
4225             /* We don't know what the board looked like before
4226                this move.  Punt. */
4227             strcpy(parseList[moveNum - 1], move_str);
4228             strcat(parseList[moveNum - 1], " ");
4229             strcat(parseList[moveNum - 1], elapsed_time);
4230             moveList[moveNum - 1][0] = NULLCHAR;
4231         } else if (strcmp(move_str, "none") == 0) {
4232             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4233             /* Again, we don't know what the board looked like;
4234                this is really the start of the game. */
4235             parseList[moveNum - 1][0] = NULLCHAR;
4236             moveList[moveNum - 1][0] = NULLCHAR;
4237             backwardMostMove = moveNum;
4238             startedFromSetupPosition = TRUE;
4239             fromX = fromY = toX = toY = -1;
4240         } else {
4241           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
4242           //                 So we parse the long-algebraic move string in stead of the SAN move
4243           int valid; char buf[MSG_SIZ], *prom;
4244
4245           // str looks something like "Q/a1-a2"; kill the slash
4246           if(str[1] == '/') 
4247                 sprintf(buf, "%c%s", str[0], str+2);
4248           else  strcpy(buf, str); // might be castling
4249           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
4250                 strcat(buf, prom); // long move lacks promo specification!
4251           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4252                 if(appData.debugMode) 
4253                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4254                 strcpy(move_str, buf);
4255           }
4256           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4257                                 &fromX, &fromY, &toX, &toY, &promoChar)
4258                || ParseOneMove(buf, moveNum - 1, &moveType,
4259                                 &fromX, &fromY, &toX, &toY, &promoChar);
4260           // end of long SAN patch
4261           if (valid) {
4262             (void) CoordsToAlgebraic(boards[moveNum - 1],
4263                                      PosFlags(moveNum - 1),
4264                                      fromY, fromX, toY, toX, promoChar,
4265                                      parseList[moveNum-1]);
4266             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4267               case MT_NONE:
4268               case MT_STALEMATE:
4269               default:
4270                 break;
4271               case MT_CHECK:
4272                 if(gameInfo.variant != VariantShogi)
4273                     strcat(parseList[moveNum - 1], "+");
4274                 break;
4275               case MT_CHECKMATE:
4276               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4277                 strcat(parseList[moveNum - 1], "#");
4278                 break;
4279             }
4280             strcat(parseList[moveNum - 1], " ");
4281             strcat(parseList[moveNum - 1], elapsed_time);
4282             /* currentMoveString is set as a side-effect of ParseOneMove */
4283             strcpy(moveList[moveNum - 1], currentMoveString);
4284             strcat(moveList[moveNum - 1], "\n");
4285           } else {
4286             /* Move from ICS was illegal!?  Punt. */
4287   if (appData.debugMode) {
4288     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4289     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4290   }
4291             strcpy(parseList[moveNum - 1], move_str);
4292             strcat(parseList[moveNum - 1], " ");
4293             strcat(parseList[moveNum - 1], elapsed_time);
4294             moveList[moveNum - 1][0] = NULLCHAR;
4295             fromX = fromY = toX = toY = -1;
4296           }
4297         }
4298   if (appData.debugMode) {
4299     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4300     setbuf(debugFP, NULL);
4301   }
4302
4303 #if ZIPPY
4304         /* Send move to chess program (BEFORE animating it). */
4305         if (appData.zippyPlay && !newGame && newMove && 
4306            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4307
4308             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4309                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4310                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4311                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4312                             move_str);
4313                     DisplayError(str, 0);
4314                 } else {
4315                     if (first.sendTime) {
4316                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4317                     }
4318                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4319                     if (firstMove && !bookHit) {
4320                         firstMove = FALSE;
4321                         if (first.useColors) {
4322                           SendToProgram(gameMode == IcsPlayingWhite ?
4323                                         "white\ngo\n" :
4324                                         "black\ngo\n", &first);
4325                         } else {
4326                           SendToProgram("go\n", &first);
4327                         }
4328                         first.maybeThinking = TRUE;
4329                     }
4330                 }
4331             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4332               if (moveList[moveNum - 1][0] == NULLCHAR) {
4333                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4334                 DisplayError(str, 0);
4335               } else {
4336                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4337                 SendMoveToProgram(moveNum - 1, &first);
4338               }
4339             }
4340         }
4341 #endif
4342     }
4343
4344     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4345         /* If move comes from a remote source, animate it.  If it
4346            isn't remote, it will have already been animated. */
4347         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4348             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4349         }
4350         if (!pausing && appData.highlightLastMove) {
4351             SetHighlights(fromX, fromY, toX, toY);
4352         }
4353     }
4354     
4355     /* Start the clocks */
4356     whiteFlag = blackFlag = FALSE;
4357     appData.clockMode = !(basetime == 0 && increment == 0);
4358     if (ticking == 0) {
4359       ics_clock_paused = TRUE;
4360       StopClocks();
4361     } else if (ticking == 1) {
4362       ics_clock_paused = FALSE;
4363     }
4364     if (gameMode == IcsIdle ||
4365         relation == RELATION_OBSERVING_STATIC ||
4366         relation == RELATION_EXAMINING ||
4367         ics_clock_paused)
4368       DisplayBothClocks();
4369     else
4370       StartClocks();
4371     
4372     /* Display opponents and material strengths */
4373     if (gameInfo.variant != VariantBughouse &&
4374         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4375         if (tinyLayout || smallLayout) {
4376             if(gameInfo.variant == VariantNormal)
4377                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4378                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4379                     basetime, increment);
4380             else
4381                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4382                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4383                     basetime, increment, (int) gameInfo.variant);
4384         } else {
4385             if(gameInfo.variant == VariantNormal)
4386                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4387                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4388                     basetime, increment);
4389             else
4390                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4391                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4392                     basetime, increment, VariantName(gameInfo.variant));
4393         }
4394         DisplayTitle(str);
4395   if (appData.debugMode) {
4396     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4397   }
4398     }
4399
4400
4401     /* Display the board */
4402     if (!pausing && !appData.noGUI) {
4403       
4404       if (appData.premove)
4405           if (!gotPremove || 
4406              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4407              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4408               ClearPremoveHighlights();
4409
4410       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4411         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4412       DrawPosition(j, boards[currentMove]);
4413
4414       DisplayMove(moveNum - 1);
4415       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4416             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4417               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4418         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4419       }
4420     }
4421
4422     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4423 #if ZIPPY
4424     if(bookHit) { // [HGM] book: simulate book reply
4425         static char bookMove[MSG_SIZ]; // a bit generous?
4426
4427         programStats.nodes = programStats.depth = programStats.time = 
4428         programStats.score = programStats.got_only_move = 0;
4429         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4430
4431         strcpy(bookMove, "move ");
4432         strcat(bookMove, bookHit);
4433         HandleMachineMove(bookMove, &first);
4434     }
4435 #endif
4436 }
4437
4438 void
4439 GetMoveListEvent()
4440 {
4441     char buf[MSG_SIZ];
4442     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4443         ics_getting_history = H_REQUESTED;
4444         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4445         SendToICS(buf);
4446     }
4447 }
4448
4449 void
4450 AnalysisPeriodicEvent(force)
4451      int force;
4452 {
4453     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4454          && !force) || !appData.periodicUpdates)
4455       return;
4456
4457     /* Send . command to Crafty to collect stats */
4458     SendToProgram(".\n", &first);
4459
4460     /* Don't send another until we get a response (this makes
4461        us stop sending to old Crafty's which don't understand
4462        the "." command (sending illegal cmds resets node count & time,
4463        which looks bad)) */
4464     programStats.ok_to_send = 0;
4465 }
4466
4467 void ics_update_width(new_width)
4468         int new_width;
4469 {
4470         ics_printf("set width %d\n", new_width);
4471 }
4472
4473 void
4474 SendMoveToProgram(moveNum, cps)
4475      int moveNum;
4476      ChessProgramState *cps;
4477 {
4478     char buf[MSG_SIZ];
4479
4480     if (cps->useUsermove) {
4481       SendToProgram("usermove ", cps);
4482     }
4483     if (cps->useSAN) {
4484       char *space;
4485       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4486         int len = space - parseList[moveNum];
4487         memcpy(buf, parseList[moveNum], len);
4488         buf[len++] = '\n';
4489         buf[len] = NULLCHAR;
4490       } else {
4491         sprintf(buf, "%s\n", parseList[moveNum]);
4492       }
4493       SendToProgram(buf, cps);
4494     } else {
4495       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4496         AlphaRank(moveList[moveNum], 4);
4497         SendToProgram(moveList[moveNum], cps);
4498         AlphaRank(moveList[moveNum], 4); // and back
4499       } else
4500       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4501        * the engine. It would be nice to have a better way to identify castle 
4502        * moves here. */
4503       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4504                                                                          && cps->useOOCastle) {
4505         int fromX = moveList[moveNum][0] - AAA; 
4506         int fromY = moveList[moveNum][1] - ONE;
4507         int toX = moveList[moveNum][2] - AAA; 
4508         int toY = moveList[moveNum][3] - ONE;
4509         if((boards[moveNum][fromY][fromX] == WhiteKing 
4510             && boards[moveNum][toY][toX] == WhiteRook)
4511            || (boards[moveNum][fromY][fromX] == BlackKing 
4512                && boards[moveNum][toY][toX] == BlackRook)) {
4513           if(toX > fromX) SendToProgram("O-O\n", cps);
4514           else SendToProgram("O-O-O\n", cps);
4515         }
4516         else SendToProgram(moveList[moveNum], cps);
4517       }
4518       else SendToProgram(moveList[moveNum], cps);
4519       /* End of additions by Tord */
4520     }
4521
4522     /* [HGM] setting up the opening has brought engine in force mode! */
4523     /*       Send 'go' if we are in a mode where machine should play. */
4524     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4525         (gameMode == TwoMachinesPlay   ||
4526 #ifdef ZIPPY
4527          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4528 #endif
4529          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4530         SendToProgram("go\n", cps);
4531   if (appData.debugMode) {
4532     fprintf(debugFP, "(extra)\n");
4533   }
4534     }
4535     setboardSpoiledMachineBlack = 0;
4536 }
4537
4538 void
4539 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4540      ChessMove moveType;
4541      int fromX, fromY, toX, toY;
4542 {
4543     char user_move[MSG_SIZ];
4544
4545     switch (moveType) {
4546       default:
4547         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4548                 (int)moveType, fromX, fromY, toX, toY);
4549         DisplayError(user_move + strlen("say "), 0);
4550         break;
4551       case WhiteKingSideCastle:
4552       case BlackKingSideCastle:
4553       case WhiteQueenSideCastleWild:
4554       case BlackQueenSideCastleWild:
4555       /* PUSH Fabien */
4556       case WhiteHSideCastleFR:
4557       case BlackHSideCastleFR:
4558       /* POP Fabien */
4559         sprintf(user_move, "o-o\n");
4560         break;
4561       case WhiteQueenSideCastle:
4562       case BlackQueenSideCastle:
4563       case WhiteKingSideCastleWild:
4564       case BlackKingSideCastleWild:
4565       /* PUSH Fabien */
4566       case WhiteASideCastleFR:
4567       case BlackASideCastleFR:
4568       /* POP Fabien */
4569         sprintf(user_move, "o-o-o\n");
4570         break;
4571       case WhitePromotionQueen:
4572       case BlackPromotionQueen:
4573       case WhitePromotionRook:
4574       case BlackPromotionRook:
4575       case WhitePromotionBishop:
4576       case BlackPromotionBishop:
4577       case WhitePromotionKnight:
4578       case BlackPromotionKnight:
4579       case WhitePromotionKing:
4580       case BlackPromotionKing:
4581       case WhitePromotionChancellor:
4582       case BlackPromotionChancellor:
4583       case WhitePromotionArchbishop:
4584       case BlackPromotionArchbishop:
4585         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4586             sprintf(user_move, "%c%c%c%c=%c\n",
4587                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4588                 PieceToChar(WhiteFerz));
4589         else if(gameInfo.variant == VariantGreat)
4590             sprintf(user_move, "%c%c%c%c=%c\n",
4591                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4592                 PieceToChar(WhiteMan));
4593         else
4594             sprintf(user_move, "%c%c%c%c=%c\n",
4595                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4596                 PieceToChar(PromoPiece(moveType)));
4597         break;
4598       case WhiteDrop:
4599       case BlackDrop:
4600         sprintf(user_move, "%c@%c%c\n",
4601                 ToUpper(PieceToChar((ChessSquare) fromX)),
4602                 AAA + toX, ONE + toY);
4603         break;
4604       case NormalMove:
4605       case WhiteCapturesEnPassant:
4606       case BlackCapturesEnPassant:
4607       case IllegalMove:  /* could be a variant we don't quite understand */
4608         sprintf(user_move, "%c%c%c%c\n",
4609                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4610         break;
4611     }
4612     SendToICS(user_move);
4613     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4614         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4615 }
4616
4617 void
4618 UploadGameEvent()
4619 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4620     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4621     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4622     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4623         DisplayError("You cannot do this while you are playing or observing", 0);
4624         return;
4625     }
4626     if(gameMode != IcsExamining) { // is this ever not the case?
4627         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4628
4629         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4630             sprintf(command, "match %s", ics_handle);
4631         } else { // on FICS we must first go to general examine mode
4632             strcpy(command, "examine\nbsetup"); // and specify variant within it with bsetups
4633         }
4634         if(gameInfo.variant != VariantNormal) {
4635             // try figure out wild number, as xboard names are not always valid on ICS
4636             for(i=1; i<=36; i++) {
4637                 sprintf(buf, "wild/%d", i);
4638                 if(StringToVariant(buf) == gameInfo.variant) break;
4639             }
4640             if(i<=36 && ics_type == ICS_ICC) sprintf(buf, "%s w%d\n", command, i);
4641             else if(i == 22) sprintf(buf, "%s fr\n", command);
4642             else sprintf(buf, "%s %s\n", command, VariantName(gameInfo.variant));
4643         } else sprintf(buf, "%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4644         SendToICS(ics_prefix);
4645         SendToICS(buf);
4646         if(startedFromSetupPosition || backwardMostMove != 0) {
4647           fen = PositionToFEN(backwardMostMove, NULL);
4648           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4649             sprintf(buf, "loadfen %s\n", fen);
4650             SendToICS(buf);
4651           } else { // FICS: everything has to set by separate bsetup commands
4652             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4653             sprintf(buf, "bsetup fen %s\n", fen);
4654             SendToICS(buf);
4655             if(!WhiteOnMove(backwardMostMove)) {
4656                 SendToICS("bsetup tomove black\n");
4657             }
4658             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4659             sprintf(buf, "bsetup wcastle %s\n", castlingStrings[i]);
4660             SendToICS(buf);
4661             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4662             sprintf(buf, "bsetup bcastle %s\n", castlingStrings[i]);
4663             SendToICS(buf);
4664             i = boards[backwardMostMove][EP_STATUS];
4665             if(i >= 0) { // set e.p.
4666                 sprintf(buf, "bsetup eppos %c\n", i+AAA);
4667                 SendToICS(buf);
4668             }
4669             bsetup++;
4670           }
4671         }
4672       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4673             SendToICS("bsetup done\n"); // switch to normal examining.
4674     }
4675     for(i = backwardMostMove; i<last; i++) {
4676         char buf[20];
4677         sprintf(buf, "%s\n", parseList[i]);
4678         SendToICS(buf);
4679     }
4680     SendToICS(ics_prefix);
4681     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4682 }
4683
4684 void
4685 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4686      int rf, ff, rt, ft;
4687      char promoChar;
4688      char move[7];
4689 {
4690     if (rf == DROP_RANK) {
4691         sprintf(move, "%c@%c%c\n",
4692                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4693     } else {
4694         if (promoChar == 'x' || promoChar == NULLCHAR) {
4695             sprintf(move, "%c%c%c%c\n",
4696                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4697         } else {
4698             sprintf(move, "%c%c%c%c%c\n",
4699                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4700         }
4701     }
4702 }
4703
4704 void
4705 ProcessICSInitScript(f)
4706      FILE *f;
4707 {
4708     char buf[MSG_SIZ];
4709
4710     while (fgets(buf, MSG_SIZ, f)) {
4711         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4712     }
4713
4714     fclose(f);
4715 }
4716
4717
4718 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4719 void
4720 AlphaRank(char *move, int n)
4721 {
4722 //    char *p = move, c; int x, y;
4723
4724     if (appData.debugMode) {
4725         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4726     }
4727
4728     if(move[1]=='*' && 
4729        move[2]>='0' && move[2]<='9' &&
4730        move[3]>='a' && move[3]<='x'    ) {
4731         move[1] = '@';
4732         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4733         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4734     } else
4735     if(move[0]>='0' && move[0]<='9' &&
4736        move[1]>='a' && move[1]<='x' &&
4737        move[2]>='0' && move[2]<='9' &&
4738        move[3]>='a' && move[3]<='x'    ) {
4739         /* input move, Shogi -> normal */
4740         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4741         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4742         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4743         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4744     } else
4745     if(move[1]=='@' &&
4746        move[3]>='0' && move[3]<='9' &&
4747        move[2]>='a' && move[2]<='x'    ) {
4748         move[1] = '*';
4749         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4750         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4751     } else
4752     if(
4753        move[0]>='a' && move[0]<='x' &&
4754        move[3]>='0' && move[3]<='9' &&
4755        move[2]>='a' && move[2]<='x'    ) {
4756          /* output move, normal -> Shogi */
4757         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4758         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4759         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4760         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4761         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4762     }
4763     if (appData.debugMode) {
4764         fprintf(debugFP, "   out = '%s'\n", move);
4765     }
4766 }
4767
4768 char yy_textstr[8000];
4769
4770 /* Parser for moves from gnuchess, ICS, or user typein box */
4771 Boolean
4772 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4773      char *move;
4774      int moveNum;
4775      ChessMove *moveType;
4776      int *fromX, *fromY, *toX, *toY;
4777      char *promoChar;
4778 {       
4779     if (appData.debugMode) {
4780         fprintf(debugFP, "move to parse: %s\n", move);
4781     }
4782     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4783
4784     switch (*moveType) {
4785       case WhitePromotionChancellor:
4786       case BlackPromotionChancellor:
4787       case WhitePromotionArchbishop:
4788       case BlackPromotionArchbishop:
4789       case WhitePromotionQueen:
4790       case BlackPromotionQueen:
4791       case WhitePromotionRook:
4792       case BlackPromotionRook:
4793       case WhitePromotionBishop:
4794       case BlackPromotionBishop:
4795       case WhitePromotionKnight:
4796       case BlackPromotionKnight:
4797       case WhitePromotionKing:
4798       case BlackPromotionKing:
4799       case NormalMove:
4800       case WhiteCapturesEnPassant:
4801       case BlackCapturesEnPassant:
4802       case WhiteKingSideCastle:
4803       case WhiteQueenSideCastle:
4804       case BlackKingSideCastle:
4805       case BlackQueenSideCastle:
4806       case WhiteKingSideCastleWild:
4807       case WhiteQueenSideCastleWild:
4808       case BlackKingSideCastleWild:
4809       case BlackQueenSideCastleWild:
4810       /* Code added by Tord: */
4811       case WhiteHSideCastleFR:
4812       case WhiteASideCastleFR:
4813       case BlackHSideCastleFR:
4814       case BlackASideCastleFR:
4815       /* End of code added by Tord */
4816       case IllegalMove:         /* bug or odd chess variant */
4817         *fromX = currentMoveString[0] - AAA;
4818         *fromY = currentMoveString[1] - ONE;
4819         *toX = currentMoveString[2] - AAA;
4820         *toY = currentMoveString[3] - ONE;
4821         *promoChar = currentMoveString[4];
4822         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4823             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4824     if (appData.debugMode) {
4825         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4826     }
4827             *fromX = *fromY = *toX = *toY = 0;
4828             return FALSE;
4829         }
4830         if (appData.testLegality) {
4831           return (*moveType != IllegalMove);
4832         } else {
4833           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4834                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4835         }
4836
4837       case WhiteDrop:
4838       case BlackDrop:
4839         *fromX = *moveType == WhiteDrop ?
4840           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4841           (int) CharToPiece(ToLower(currentMoveString[0]));
4842         *fromY = DROP_RANK;
4843         *toX = currentMoveString[2] - AAA;
4844         *toY = currentMoveString[3] - ONE;
4845         *promoChar = NULLCHAR;
4846         return TRUE;
4847
4848       case AmbiguousMove:
4849       case ImpossibleMove:
4850       case (ChessMove) 0:       /* end of file */
4851       case ElapsedTime:
4852       case Comment:
4853       case PGNTag:
4854       case NAG:
4855       case WhiteWins:
4856       case BlackWins:
4857       case GameIsDrawn:
4858       default:
4859     if (appData.debugMode) {
4860         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4861     }
4862         /* bug? */
4863         *fromX = *fromY = *toX = *toY = 0;
4864         *promoChar = NULLCHAR;
4865         return FALSE;
4866     }
4867 }
4868
4869
4870 void
4871 ParsePV(char *pv, Boolean storeComments)
4872 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4873   int fromX, fromY, toX, toY; char promoChar;
4874   ChessMove moveType;
4875   Boolean valid;
4876   int nr = 0;
4877
4878   endPV = forwardMostMove;
4879   do {
4880     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4881     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4882     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4883 if(appData.debugMode){
4884 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);
4885 }
4886     if(!valid && nr == 0 &&
4887        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4888         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4889         // Hande case where played move is different from leading PV move
4890         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4891         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4892         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4893         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4894           endPV += 2; // if position different, keep this
4895           moveList[endPV-1][0] = fromX + AAA;
4896           moveList[endPV-1][1] = fromY + ONE;
4897           moveList[endPV-1][2] = toX + AAA;
4898           moveList[endPV-1][3] = toY + ONE;
4899           parseList[endPV-1][0] = NULLCHAR;
4900           strcpy(moveList[endPV-2], "_0_0"); // suppress premove highlight on takeback move
4901         }
4902       }
4903     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4904     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4905     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4906     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4907         valid++; // allow comments in PV
4908         continue;
4909     }
4910     nr++;
4911     if(endPV+1 > framePtr) break; // no space, truncate
4912     if(!valid) break;
4913     endPV++;
4914     CopyBoard(boards[endPV], boards[endPV-1]);
4915     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4916     moveList[endPV-1][0] = fromX + AAA;
4917     moveList[endPV-1][1] = fromY + ONE;
4918     moveList[endPV-1][2] = toX + AAA;
4919     moveList[endPV-1][3] = toY + ONE;
4920     if(storeComments)
4921         CoordsToAlgebraic(boards[endPV - 1],
4922                              PosFlags(endPV - 1),
4923                              fromY, fromX, toY, toX, promoChar,
4924                              parseList[endPV - 1]);
4925     else
4926         parseList[endPV-1][0] = NULLCHAR;
4927   } while(valid);
4928   currentMove = endPV;
4929   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4930   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4931                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4932   DrawPosition(TRUE, boards[currentMove]);
4933 }
4934
4935 static int lastX, lastY;
4936
4937 Boolean
4938 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4939 {
4940         int startPV;
4941         char *p;
4942
4943         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4944         lastX = x; lastY = y;
4945         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4946         startPV = index;
4947         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4948         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
4949         index = startPV;
4950         do{ while(buf[index] && buf[index] != '\n') index++;
4951         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
4952         buf[index] = 0;
4953         ParsePV(buf+startPV, FALSE);
4954         *start = startPV; *end = index-1;
4955         return TRUE;
4956 }
4957
4958 Boolean
4959 LoadPV(int x, int y)
4960 { // called on right mouse click to load PV
4961   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4962   lastX = x; lastY = y;
4963   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
4964   return TRUE;
4965 }
4966
4967 void
4968 UnLoadPV()
4969 {
4970   if(endPV < 0) return;
4971   endPV = -1;
4972   currentMove = forwardMostMove;
4973   ClearPremoveHighlights();
4974   DrawPosition(TRUE, boards[currentMove]);
4975 }
4976
4977 void
4978 MovePV(int x, int y, int h)
4979 { // step through PV based on mouse coordinates (called on mouse move)
4980   int margin = h>>3, step = 0;
4981
4982   if(endPV < 0) return;
4983   // we must somehow check if right button is still down (might be released off board!)
4984   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4985   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4986   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4987   if(!step) return;
4988   lastX = x; lastY = y;
4989   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4990   currentMove += step;
4991   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4992   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4993                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4994   DrawPosition(FALSE, boards[currentMove]);
4995 }
4996
4997
4998 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4999 // All positions will have equal probability, but the current method will not provide a unique
5000 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5001 #define DARK 1
5002 #define LITE 2
5003 #define ANY 3
5004
5005 int squaresLeft[4];
5006 int piecesLeft[(int)BlackPawn];
5007 int seed, nrOfShuffles;
5008
5009 void GetPositionNumber()
5010 {       // sets global variable seed
5011         int i;
5012
5013         seed = appData.defaultFrcPosition;
5014         if(seed < 0) { // randomize based on time for negative FRC position numbers
5015                 for(i=0; i<50; i++) seed += random();
5016                 seed = random() ^ random() >> 8 ^ random() << 8;
5017                 if(seed<0) seed = -seed;
5018         }
5019 }
5020
5021 int put(Board board, int pieceType, int rank, int n, int shade)
5022 // put the piece on the (n-1)-th empty squares of the given shade
5023 {
5024         int i;
5025
5026         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5027                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5028                         board[rank][i] = (ChessSquare) pieceType;
5029                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5030                         squaresLeft[ANY]--;
5031                         piecesLeft[pieceType]--; 
5032                         return i;
5033                 }
5034         }
5035         return -1;
5036 }
5037
5038
5039 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5040 // calculate where the next piece goes, (any empty square), and put it there
5041 {
5042         int i;
5043
5044         i = seed % squaresLeft[shade];
5045         nrOfShuffles *= squaresLeft[shade];
5046         seed /= squaresLeft[shade];
5047         put(board, pieceType, rank, i, shade);
5048 }
5049
5050 void AddTwoPieces(Board board, int pieceType, int rank)
5051 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5052 {
5053         int i, n=squaresLeft[ANY], j=n-1, k;
5054
5055         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5056         i = seed % k;  // pick one
5057         nrOfShuffles *= k;
5058         seed /= k;
5059         while(i >= j) i -= j--;
5060         j = n - 1 - j; i += j;
5061         put(board, pieceType, rank, j, ANY);
5062         put(board, pieceType, rank, i, ANY);
5063 }
5064
5065 void SetUpShuffle(Board board, int number)
5066 {
5067         int i, p, first=1;
5068
5069         GetPositionNumber(); nrOfShuffles = 1;
5070
5071         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5072         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5073         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5074
5075         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5076
5077         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5078             p = (int) board[0][i];
5079             if(p < (int) BlackPawn) piecesLeft[p] ++;
5080             board[0][i] = EmptySquare;
5081         }
5082
5083         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5084             // shuffles restricted to allow normal castling put KRR first
5085             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5086                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5087             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5088                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5089             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5090                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5091             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5092                 put(board, WhiteRook, 0, 0, ANY);
5093             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5094         }
5095
5096         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5097             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5098             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5099                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5100                 while(piecesLeft[p] >= 2) {
5101                     AddOnePiece(board, p, 0, LITE);
5102                     AddOnePiece(board, p, 0, DARK);
5103                 }
5104                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5105             }
5106
5107         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5108             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5109             // but we leave King and Rooks for last, to possibly obey FRC restriction
5110             if(p == (int)WhiteRook) continue;
5111             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5112             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5113         }
5114
5115         // now everything is placed, except perhaps King (Unicorn) and Rooks
5116
5117         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5118             // Last King gets castling rights
5119             while(piecesLeft[(int)WhiteUnicorn]) {
5120                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5121                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5122             }
5123
5124             while(piecesLeft[(int)WhiteKing]) {
5125                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5126                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5127             }
5128
5129
5130         } else {
5131             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5132             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5133         }
5134
5135         // Only Rooks can be left; simply place them all
5136         while(piecesLeft[(int)WhiteRook]) {
5137                 i = put(board, WhiteRook, 0, 0, ANY);
5138                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5139                         if(first) {
5140                                 first=0;
5141                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5142                         }
5143                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5144                 }
5145         }
5146         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5147             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5148         }
5149
5150         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5151 }
5152
5153 int SetCharTable( char *table, const char * map )
5154 /* [HGM] moved here from winboard.c because of its general usefulness */
5155 /*       Basically a safe strcpy that uses the last character as King */
5156 {
5157     int result = FALSE; int NrPieces;
5158
5159     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
5160                     && NrPieces >= 12 && !(NrPieces&1)) {
5161         int i; /* [HGM] Accept even length from 12 to 34 */
5162
5163         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5164         for( i=0; i<NrPieces/2-1; i++ ) {
5165             table[i] = map[i];
5166             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5167         }
5168         table[(int) WhiteKing]  = map[NrPieces/2-1];
5169         table[(int) BlackKing]  = map[NrPieces-1];
5170
5171         result = TRUE;
5172     }
5173
5174     return result;
5175 }
5176
5177 void Prelude(Board board)
5178 {       // [HGM] superchess: random selection of exo-pieces
5179         int i, j, k; ChessSquare p; 
5180         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5181
5182         GetPositionNumber(); // use FRC position number
5183
5184         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5185             SetCharTable(pieceToChar, appData.pieceToCharTable);
5186             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
5187                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5188         }
5189
5190         j = seed%4;                 seed /= 4; 
5191         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5192         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5193         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5194         j = seed%3 + (seed%3 >= j); seed /= 3; 
5195         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5196         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5197         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5198         j = seed%3;                 seed /= 3; 
5199         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5200         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5201         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5202         j = seed%2 + (seed%2 >= j); seed /= 2; 
5203         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5204         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5205         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5206         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5207         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5208         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5209         put(board, exoPieces[0],    0, 0, ANY);
5210         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5211 }
5212
5213 void
5214 InitPosition(redraw)
5215      int redraw;
5216 {
5217     ChessSquare (* pieces)[BOARD_FILES];
5218     int i, j, pawnRow, overrule,
5219     oldx = gameInfo.boardWidth,
5220     oldy = gameInfo.boardHeight,
5221     oldh = gameInfo.holdingsWidth,
5222     oldv = gameInfo.variant;
5223
5224     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5225
5226     /* [AS] Initialize pv info list [HGM] and game status */
5227     {
5228         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5229             pvInfoList[i].depth = 0;
5230             boards[i][EP_STATUS] = EP_NONE;
5231             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5232         }
5233
5234         initialRulePlies = 0; /* 50-move counter start */
5235
5236         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5237         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5238     }
5239
5240     
5241     /* [HGM] logic here is completely changed. In stead of full positions */
5242     /* the initialized data only consist of the two backranks. The switch */
5243     /* selects which one we will use, which is than copied to the Board   */
5244     /* initialPosition, which for the rest is initialized by Pawns and    */
5245     /* empty squares. This initial position is then copied to boards[0],  */
5246     /* possibly after shuffling, so that it remains available.            */
5247
5248     gameInfo.holdingsWidth = 0; /* default board sizes */
5249     gameInfo.boardWidth    = 8;
5250     gameInfo.boardHeight   = 8;
5251     gameInfo.holdingsSize  = 0;
5252     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5253     for(i=0; i<BOARD_FILES-2; i++)
5254       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5255     initialPosition[EP_STATUS] = EP_NONE;
5256     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5257     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5258          SetCharTable(pieceNickName, appData.pieceNickNames);
5259     else SetCharTable(pieceNickName, "............");
5260
5261     switch (gameInfo.variant) {
5262     case VariantFischeRandom:
5263       shuffleOpenings = TRUE;
5264     default:
5265       pieces = FIDEArray;
5266       break;
5267     case VariantShatranj:
5268       pieces = ShatranjArray;
5269       nrCastlingRights = 0;
5270       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
5271       break;
5272     case VariantMakruk:
5273       pieces = makrukArray;
5274       nrCastlingRights = 0;
5275       startedFromSetupPosition = TRUE;
5276       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
5277       break;
5278     case VariantTwoKings:
5279       pieces = twoKingsArray;
5280       break;
5281     case VariantCapaRandom:
5282       shuffleOpenings = TRUE;
5283     case VariantCapablanca:
5284       pieces = CapablancaArray;
5285       gameInfo.boardWidth = 10;
5286       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5287       break;
5288     case VariantGothic:
5289       pieces = GothicArray;
5290       gameInfo.boardWidth = 10;
5291       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5292       break;
5293     case VariantJanus:
5294       pieces = JanusArray;
5295       gameInfo.boardWidth = 10;
5296       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
5297       nrCastlingRights = 6;
5298         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5299         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5300         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5301         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5302         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5303         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5304       break;
5305     case VariantFalcon:
5306       pieces = FalconArray;
5307       gameInfo.boardWidth = 10;
5308       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
5309       break;
5310     case VariantXiangqi:
5311       pieces = XiangqiArray;
5312       gameInfo.boardWidth  = 9;
5313       gameInfo.boardHeight = 10;
5314       nrCastlingRights = 0;
5315       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
5316       break;
5317     case VariantShogi:
5318       pieces = ShogiArray;
5319       gameInfo.boardWidth  = 9;
5320       gameInfo.boardHeight = 9;
5321       gameInfo.holdingsSize = 7;
5322       nrCastlingRights = 0;
5323       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
5324       break;
5325     case VariantCourier:
5326       pieces = CourierArray;
5327       gameInfo.boardWidth  = 12;
5328       nrCastlingRights = 0;
5329       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5330       break;
5331     case VariantKnightmate:
5332       pieces = KnightmateArray;
5333       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
5334       break;
5335     case VariantFairy:
5336       pieces = fairyArray;
5337       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5338       break;
5339     case VariantGreat:
5340       pieces = GreatArray;
5341       gameInfo.boardWidth = 10;
5342       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5343       gameInfo.holdingsSize = 8;
5344       break;
5345     case VariantSuper:
5346       pieces = FIDEArray;
5347       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5348       gameInfo.holdingsSize = 8;
5349       startedFromSetupPosition = TRUE;
5350       break;
5351     case VariantCrazyhouse:
5352     case VariantBughouse:
5353       pieces = FIDEArray;
5354       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
5355       gameInfo.holdingsSize = 5;
5356       break;
5357     case VariantWildCastle:
5358       pieces = FIDEArray;
5359       /* !!?shuffle with kings guaranteed to be on d or e file */
5360       shuffleOpenings = 1;
5361       break;
5362     case VariantNoCastle:
5363       pieces = FIDEArray;
5364       nrCastlingRights = 0;
5365       /* !!?unconstrained back-rank shuffle */
5366       shuffleOpenings = 1;
5367       break;
5368     }
5369
5370     overrule = 0;
5371     if(appData.NrFiles >= 0) {
5372         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5373         gameInfo.boardWidth = appData.NrFiles;
5374     }
5375     if(appData.NrRanks >= 0) {
5376         gameInfo.boardHeight = appData.NrRanks;
5377     }
5378     if(appData.holdingsSize >= 0) {
5379         i = appData.holdingsSize;
5380         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5381         gameInfo.holdingsSize = i;
5382     }
5383     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5384     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5385         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5386
5387     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5388     if(pawnRow < 1) pawnRow = 1;
5389     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5390
5391     /* User pieceToChar list overrules defaults */
5392     if(appData.pieceToCharTable != NULL)
5393         SetCharTable(pieceToChar, appData.pieceToCharTable);
5394
5395     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5396
5397         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5398             s = (ChessSquare) 0; /* account holding counts in guard band */
5399         for( i=0; i<BOARD_HEIGHT; i++ )
5400             initialPosition[i][j] = s;
5401
5402         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5403         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5404         initialPosition[pawnRow][j] = WhitePawn;
5405         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5406         if(gameInfo.variant == VariantXiangqi) {
5407             if(j&1) {
5408                 initialPosition[pawnRow][j] = 
5409                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5410                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5411                    initialPosition[2][j] = WhiteCannon;
5412                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5413                 }
5414             }
5415         }
5416         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5417     }
5418     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5419
5420             j=BOARD_LEFT+1;
5421             initialPosition[1][j] = WhiteBishop;
5422             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5423             j=BOARD_RGHT-2;
5424             initialPosition[1][j] = WhiteRook;
5425             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5426     }
5427
5428     if( nrCastlingRights == -1) {
5429         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5430         /*       This sets default castling rights from none to normal corners   */
5431         /* Variants with other castling rights must set them themselves above    */
5432         nrCastlingRights = 6;
5433        
5434         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5435         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5436         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5437         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5438         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5439         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5440      }
5441
5442      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5443      if(gameInfo.variant == VariantGreat) { // promotion commoners
5444         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5445         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5446         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5447         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5448      }
5449   if (appData.debugMode) {
5450     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5451   }
5452     if(shuffleOpenings) {
5453         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5454         startedFromSetupPosition = TRUE;
5455     }
5456     if(startedFromPositionFile) {
5457       /* [HGM] loadPos: use PositionFile for every new game */
5458       CopyBoard(initialPosition, filePosition);
5459       for(i=0; i<nrCastlingRights; i++)
5460           initialRights[i] = filePosition[CASTLING][i];
5461       startedFromSetupPosition = TRUE;
5462     }
5463
5464     CopyBoard(boards[0], initialPosition);
5465
5466     if(oldx != gameInfo.boardWidth ||
5467        oldy != gameInfo.boardHeight ||
5468        oldh != gameInfo.holdingsWidth
5469 #ifdef GOTHIC
5470        || oldv == VariantGothic ||        // For licensing popups
5471        gameInfo.variant == VariantGothic
5472 #endif
5473 #ifdef FALCON
5474        || oldv == VariantFalcon ||
5475        gameInfo.variant == VariantFalcon
5476 #endif
5477                                          )
5478             InitDrawingSizes(-2 ,0);
5479
5480     if (redraw)
5481       DrawPosition(TRUE, boards[currentMove]);
5482 }
5483
5484 void
5485 SendBoard(cps, moveNum)
5486      ChessProgramState *cps;
5487      int moveNum;
5488 {
5489     char message[MSG_SIZ];
5490     
5491     if (cps->useSetboard) {
5492       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5493       sprintf(message, "setboard %s\n", fen);
5494       SendToProgram(message, cps);
5495       free(fen);
5496
5497     } else {
5498       ChessSquare *bp;
5499       int i, j;
5500       /* Kludge to set black to move, avoiding the troublesome and now
5501        * deprecated "black" command.
5502        */
5503       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5504
5505       SendToProgram("edit\n", cps);
5506       SendToProgram("#\n", cps);
5507       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5508         bp = &boards[moveNum][i][BOARD_LEFT];
5509         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5510           if ((int) *bp < (int) BlackPawn) {
5511             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5512                     AAA + j, ONE + i);
5513             if(message[0] == '+' || message[0] == '~') {
5514                 sprintf(message, "%c%c%c+\n",
5515                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5516                         AAA + j, ONE + i);
5517             }
5518             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5519                 message[1] = BOARD_RGHT   - 1 - j + '1';
5520                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5521             }
5522             SendToProgram(message, cps);
5523           }
5524         }
5525       }
5526     
5527       SendToProgram("c\n", cps);
5528       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5529         bp = &boards[moveNum][i][BOARD_LEFT];
5530         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5531           if (((int) *bp != (int) EmptySquare)
5532               && ((int) *bp >= (int) BlackPawn)) {
5533             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5534                     AAA + j, ONE + i);
5535             if(message[0] == '+' || message[0] == '~') {
5536                 sprintf(message, "%c%c%c+\n",
5537                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5538                         AAA + j, ONE + i);
5539             }
5540             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5541                 message[1] = BOARD_RGHT   - 1 - j + '1';
5542                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5543             }
5544             SendToProgram(message, cps);
5545           }
5546         }
5547       }
5548     
5549       SendToProgram(".\n", cps);
5550     }
5551     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5552 }
5553
5554 static int autoQueen; // [HGM] oneclick
5555
5556 int
5557 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5558 {
5559     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5560     /* [HGM] add Shogi promotions */
5561     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5562     ChessSquare piece;
5563     ChessMove moveType;
5564     Boolean premove;
5565
5566     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5567     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5568
5569     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5570       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5571         return FALSE;
5572
5573     piece = boards[currentMove][fromY][fromX];
5574     if(gameInfo.variant == VariantShogi) {
5575         promotionZoneSize = 3;
5576         highestPromotingPiece = (int)WhiteFerz;
5577     } else if(gameInfo.variant == VariantMakruk) {
5578         promotionZoneSize = 3;
5579     }
5580
5581     // next weed out all moves that do not touch the promotion zone at all
5582     if((int)piece >= BlackPawn) {
5583         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5584              return FALSE;
5585         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5586     } else {
5587         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5588            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5589     }
5590
5591     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5592
5593     // weed out mandatory Shogi promotions
5594     if(gameInfo.variant == VariantShogi) {
5595         if(piece >= BlackPawn) {
5596             if(toY == 0 && piece == BlackPawn ||
5597                toY == 0 && piece == BlackQueen ||
5598                toY <= 1 && piece == BlackKnight) {
5599                 *promoChoice = '+';
5600                 return FALSE;
5601             }
5602         } else {
5603             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5604                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5605                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5606                 *promoChoice = '+';
5607                 return FALSE;
5608             }
5609         }
5610     }
5611
5612     // weed out obviously illegal Pawn moves
5613     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5614         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5615         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5616         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5617         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5618         // note we are not allowed to test for valid (non-)capture, due to premove
5619     }
5620
5621     // we either have a choice what to promote to, or (in Shogi) whether to promote
5622     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5623         *promoChoice = PieceToChar(BlackFerz);  // no choice
5624         return FALSE;
5625     }
5626     if(autoQueen) { // predetermined
5627         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5628              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5629         else *promoChoice = PieceToChar(BlackQueen);
5630         return FALSE;
5631     }
5632
5633     // suppress promotion popup on illegal moves that are not premoves
5634     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5635               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5636     if(appData.testLegality && !premove) {
5637         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5638                         fromY, fromX, toY, toX, NULLCHAR);
5639         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5640            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5641             return FALSE;
5642     }
5643
5644     return TRUE;
5645 }
5646
5647 int
5648 InPalace(row, column)
5649      int row, column;
5650 {   /* [HGM] for Xiangqi */
5651     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5652          column < (BOARD_WIDTH + 4)/2 &&
5653          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5654     return FALSE;
5655 }
5656
5657 int
5658 PieceForSquare (x, y)
5659      int x;
5660      int y;
5661 {
5662   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5663      return -1;
5664   else
5665      return boards[currentMove][y][x];
5666 }
5667
5668 int
5669 OKToStartUserMove(x, y)
5670      int x, y;
5671 {
5672     ChessSquare from_piece;
5673     int white_piece;
5674
5675     if (matchMode) return FALSE;
5676     if (gameMode == EditPosition) return TRUE;
5677
5678     if (x >= 0 && y >= 0)
5679       from_piece = boards[currentMove][y][x];
5680     else
5681       from_piece = EmptySquare;
5682
5683     if (from_piece == EmptySquare) return FALSE;
5684
5685     white_piece = (int)from_piece >= (int)WhitePawn &&
5686       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5687
5688     switch (gameMode) {
5689       case PlayFromGameFile:
5690       case AnalyzeFile:
5691       case TwoMachinesPlay:
5692       case EndOfGame:
5693         return FALSE;
5694
5695       case IcsObserving:
5696       case IcsIdle:
5697         return FALSE;
5698
5699       case MachinePlaysWhite:
5700       case IcsPlayingBlack:
5701         if (appData.zippyPlay) return FALSE;
5702         if (white_piece) {
5703             DisplayMoveError(_("You are playing Black"));
5704             return FALSE;
5705         }
5706         break;
5707
5708       case MachinePlaysBlack:
5709       case IcsPlayingWhite:
5710         if (appData.zippyPlay) return FALSE;
5711         if (!white_piece) {
5712             DisplayMoveError(_("You are playing White"));
5713             return FALSE;
5714         }
5715         break;
5716
5717       case EditGame:
5718         if (!white_piece && WhiteOnMove(currentMove)) {
5719             DisplayMoveError(_("It is White's turn"));
5720             return FALSE;
5721         }           
5722         if (white_piece && !WhiteOnMove(currentMove)) {
5723             DisplayMoveError(_("It is Black's turn"));
5724             return FALSE;
5725         }           
5726         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5727             /* Editing correspondence game history */
5728             /* Could disallow this or prompt for confirmation */
5729             cmailOldMove = -1;
5730         }
5731         break;
5732
5733       case BeginningOfGame:
5734         if (appData.icsActive) return FALSE;
5735         if (!appData.noChessProgram) {
5736             if (!white_piece) {
5737                 DisplayMoveError(_("You are playing White"));
5738                 return FALSE;
5739             }
5740         }
5741         break;
5742         
5743       case Training:
5744         if (!white_piece && WhiteOnMove(currentMove)) {
5745             DisplayMoveError(_("It is White's turn"));
5746             return FALSE;
5747         }           
5748         if (white_piece && !WhiteOnMove(currentMove)) {
5749             DisplayMoveError(_("It is Black's turn"));
5750             return FALSE;
5751         }           
5752         break;
5753
5754       default:
5755       case IcsExamining:
5756         break;
5757     }
5758     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5759         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5760         && gameMode != AnalyzeFile && gameMode != Training) {
5761         DisplayMoveError(_("Displayed position is not current"));
5762         return FALSE;
5763     }
5764     return TRUE;
5765 }
5766
5767 Boolean
5768 OnlyMove(int *x, int *y, Boolean captures) {
5769     DisambiguateClosure cl;
5770     if (appData.zippyPlay) return FALSE;
5771     switch(gameMode) {
5772       case MachinePlaysBlack:
5773       case IcsPlayingWhite:
5774       case BeginningOfGame:
5775         if(!WhiteOnMove(currentMove)) return FALSE;
5776         break;
5777       case MachinePlaysWhite:
5778       case IcsPlayingBlack:
5779         if(WhiteOnMove(currentMove)) return FALSE;
5780         break;
5781       default:
5782         return FALSE;
5783     }
5784     cl.pieceIn = EmptySquare; 
5785     cl.rfIn = *y;
5786     cl.ffIn = *x;
5787     cl.rtIn = -1;
5788     cl.ftIn = -1;
5789     cl.promoCharIn = NULLCHAR;
5790     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5791     if( cl.kind == NormalMove ||
5792         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5793         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5794         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5795         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5796       fromX = cl.ff;
5797       fromY = cl.rf;
5798       *x = cl.ft;
5799       *y = cl.rt;
5800       return TRUE;
5801     }
5802     if(cl.kind != ImpossibleMove) return FALSE;
5803     cl.pieceIn = EmptySquare;
5804     cl.rfIn = -1;
5805     cl.ffIn = -1;
5806     cl.rtIn = *y;
5807     cl.ftIn = *x;
5808     cl.promoCharIn = NULLCHAR;
5809     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5810     if( cl.kind == NormalMove ||
5811         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5812         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5813         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5814         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5815       fromX = cl.ff;
5816       fromY = cl.rf;
5817       *x = cl.ft;
5818       *y = cl.rt;
5819       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5820       return TRUE;
5821     }
5822     return FALSE;
5823 }
5824
5825 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5826 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5827 int lastLoadGameUseList = FALSE;
5828 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5829 ChessMove lastLoadGameStart = (ChessMove) 0;
5830
5831 ChessMove
5832 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5833      int fromX, fromY, toX, toY;
5834      int promoChar;
5835      Boolean captureOwn;
5836 {
5837     ChessMove moveType;
5838     ChessSquare pdown, pup;
5839
5840     /* Check if the user is playing in turn.  This is complicated because we
5841        let the user "pick up" a piece before it is his turn.  So the piece he
5842        tried to pick up may have been captured by the time he puts it down!
5843        Therefore we use the color the user is supposed to be playing in this
5844        test, not the color of the piece that is currently on the starting
5845        square---except in EditGame mode, where the user is playing both
5846        sides; fortunately there the capture race can't happen.  (It can
5847        now happen in IcsExamining mode, but that's just too bad.  The user
5848        will get a somewhat confusing message in that case.)
5849        */
5850
5851     switch (gameMode) {
5852       case PlayFromGameFile:
5853       case AnalyzeFile:
5854       case TwoMachinesPlay:
5855       case EndOfGame:
5856       case IcsObserving:
5857       case IcsIdle:
5858         /* We switched into a game mode where moves are not accepted,
5859            perhaps while the mouse button was down. */
5860         return ImpossibleMove;
5861
5862       case MachinePlaysWhite:
5863         /* User is moving for Black */
5864         if (WhiteOnMove(currentMove)) {
5865             DisplayMoveError(_("It is White's turn"));
5866             return ImpossibleMove;
5867         }
5868         break;
5869
5870       case MachinePlaysBlack:
5871         /* User is moving for White */
5872         if (!WhiteOnMove(currentMove)) {
5873             DisplayMoveError(_("It is Black's turn"));
5874             return ImpossibleMove;
5875         }
5876         break;
5877
5878       case EditGame:
5879       case IcsExamining:
5880       case BeginningOfGame:
5881       case AnalyzeMode:
5882       case Training:
5883         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5884             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5885             /* User is moving for Black */
5886             if (WhiteOnMove(currentMove)) {
5887                 DisplayMoveError(_("It is White's turn"));
5888                 return ImpossibleMove;
5889             }
5890         } else {
5891             /* User is moving for White */
5892             if (!WhiteOnMove(currentMove)) {
5893                 DisplayMoveError(_("It is Black's turn"));
5894                 return ImpossibleMove;
5895             }
5896         }
5897         break;
5898
5899       case IcsPlayingBlack:
5900         /* User is moving for Black */
5901         if (WhiteOnMove(currentMove)) {
5902             if (!appData.premove) {
5903                 DisplayMoveError(_("It is White's turn"));
5904             } else if (toX >= 0 && toY >= 0) {
5905                 premoveToX = toX;
5906                 premoveToY = toY;
5907                 premoveFromX = fromX;
5908                 premoveFromY = fromY;
5909                 premovePromoChar = promoChar;
5910                 gotPremove = 1;
5911                 if (appData.debugMode) 
5912                     fprintf(debugFP, "Got premove: fromX %d,"
5913                             "fromY %d, toX %d, toY %d\n",
5914                             fromX, fromY, toX, toY);
5915             }
5916             return ImpossibleMove;
5917         }
5918         break;
5919
5920       case IcsPlayingWhite:
5921         /* User is moving for White */
5922         if (!WhiteOnMove(currentMove)) {
5923             if (!appData.premove) {
5924                 DisplayMoveError(_("It is Black's turn"));
5925             } else if (toX >= 0 && toY >= 0) {
5926                 premoveToX = toX;
5927                 premoveToY = toY;
5928                 premoveFromX = fromX;
5929                 premoveFromY = fromY;
5930                 premovePromoChar = promoChar;
5931                 gotPremove = 1;
5932                 if (appData.debugMode) 
5933                     fprintf(debugFP, "Got premove: fromX %d,"
5934                             "fromY %d, toX %d, toY %d\n",
5935                             fromX, fromY, toX, toY);
5936             }
5937             return ImpossibleMove;
5938         }
5939         break;
5940
5941       default:
5942         break;
5943
5944       case EditPosition:
5945         /* EditPosition, empty square, or different color piece;
5946            click-click move is possible */
5947         if (toX == -2 || toY == -2) {
5948             boards[0][fromY][fromX] = EmptySquare;
5949             return AmbiguousMove;
5950         } else if (toX >= 0 && toY >= 0) {
5951             boards[0][toY][toX] = boards[0][fromY][fromX];
5952             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5953                 if(boards[0][fromY][0] != EmptySquare) {
5954                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5955                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5956                 }
5957             } else
5958             if(fromX == BOARD_RGHT+1) {
5959                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5960                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5961                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5962                 }
5963             } else
5964             boards[0][fromY][fromX] = EmptySquare;
5965             return AmbiguousMove;
5966         }
5967         return ImpossibleMove;
5968     }
5969
5970     if(toX < 0 || toY < 0) return ImpossibleMove;
5971     pdown = boards[currentMove][fromY][fromX];
5972     pup = boards[currentMove][toY][toX];
5973
5974     /* [HGM] If move started in holdings, it means a drop */
5975     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5976          if( pup != EmptySquare ) return ImpossibleMove;
5977          if(appData.testLegality) {
5978              /* it would be more logical if LegalityTest() also figured out
5979               * which drops are legal. For now we forbid pawns on back rank.
5980               * Shogi is on its own here...
5981               */
5982              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5983                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5984                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5985          }
5986          return WhiteDrop; /* Not needed to specify white or black yet */
5987     }
5988
5989     /* [HGM] always test for legality, to get promotion info */
5990     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5991                                          fromY, fromX, toY, toX, promoChar);
5992     /* [HGM] but possibly ignore an IllegalMove result */
5993     if (appData.testLegality) {
5994         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5995             DisplayMoveError(_("Illegal move"));
5996             return ImpossibleMove;
5997         }
5998     }
5999
6000     return moveType;
6001     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
6002        function is made into one that returns an OK move type if FinishMove
6003        should be called. This to give the calling driver routine the
6004        opportunity to finish the userMove input with a promotion popup,
6005        without bothering the user with this for invalid or illegal moves */
6006
6007 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
6008 }
6009
6010 /* Common tail of UserMoveEvent and DropMenuEvent */
6011 int
6012 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6013      ChessMove moveType;
6014      int fromX, fromY, toX, toY;
6015      /*char*/int promoChar;
6016 {
6017     char *bookHit = 0;
6018
6019     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
6020         // [HGM] superchess: suppress promotions to non-available piece
6021         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6022         if(WhiteOnMove(currentMove)) {
6023             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6024         } else {
6025             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6026         }
6027     }
6028
6029     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6030        move type in caller when we know the move is a legal promotion */
6031     if(moveType == NormalMove && promoChar)
6032         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
6033
6034     /* [HGM] convert drag-and-drop piece drops to standard form */
6035     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
6036          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6037            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6038                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6039            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6040            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6041            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6042            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6043          fromY = DROP_RANK;
6044     }
6045
6046     /* [HGM] <popupFix> The following if has been moved here from
6047        UserMoveEvent(). Because it seemed to belong here (why not allow
6048        piece drops in training games?), and because it can only be
6049        performed after it is known to what we promote. */
6050     if (gameMode == Training) {
6051       /* compare the move played on the board to the next move in the
6052        * game. If they match, display the move and the opponent's response. 
6053        * If they don't match, display an error message.
6054        */
6055       int saveAnimate;
6056       Board testBoard;
6057       CopyBoard(testBoard, boards[currentMove]);
6058       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6059
6060       if (CompareBoards(testBoard, boards[currentMove+1])) {
6061         ForwardInner(currentMove+1);
6062
6063         /* Autoplay the opponent's response.
6064          * if appData.animate was TRUE when Training mode was entered,
6065          * the response will be animated.
6066          */
6067         saveAnimate = appData.animate;
6068         appData.animate = animateTraining;
6069         ForwardInner(currentMove+1);
6070         appData.animate = saveAnimate;
6071
6072         /* check for the end of the game */
6073         if (currentMove >= forwardMostMove) {
6074           gameMode = PlayFromGameFile;
6075           ModeHighlight();
6076           SetTrainingModeOff();
6077           DisplayInformation(_("End of game"));
6078         }
6079       } else {
6080         DisplayError(_("Incorrect move"), 0);
6081       }
6082       return 1;
6083     }
6084
6085   /* Ok, now we know that the move is good, so we can kill
6086      the previous line in Analysis Mode */
6087   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
6088                                 && currentMove < forwardMostMove) {
6089     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6090   }
6091
6092   /* If we need the chess program but it's dead, restart it */
6093   ResurrectChessProgram();
6094
6095   /* A user move restarts a paused game*/
6096   if (pausing)
6097     PauseEvent();
6098
6099   thinkOutput[0] = NULLCHAR;
6100
6101   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6102
6103   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6104
6105   if (gameMode == BeginningOfGame) {
6106     if (appData.noChessProgram) {
6107       gameMode = EditGame;
6108       SetGameInfo();
6109     } else {
6110       char buf[MSG_SIZ];
6111       gameMode = MachinePlaysBlack;
6112       StartClocks();
6113       SetGameInfo();
6114       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
6115       DisplayTitle(buf);
6116       if (first.sendName) {
6117         sprintf(buf, "name %s\n", gameInfo.white);
6118         SendToProgram(buf, &first);
6119       }
6120       StartClocks();
6121     }
6122     ModeHighlight();
6123   }
6124
6125   /* Relay move to ICS or chess engine */
6126   if (appData.icsActive) {
6127     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6128         gameMode == IcsExamining) {
6129       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6130         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6131         SendToICS("draw ");
6132         SendMoveToICS(moveType, fromX, fromY, toX, toY);
6133       }
6134       // also send plain move, in case ICS does not understand atomic claims
6135       SendMoveToICS(moveType, fromX, fromY, toX, toY);
6136       ics_user_moved = 1;
6137     }
6138   } else {
6139     if (first.sendTime && (gameMode == BeginningOfGame ||
6140                            gameMode == MachinePlaysWhite ||
6141                            gameMode == MachinePlaysBlack)) {
6142       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6143     }
6144     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6145          // [HGM] book: if program might be playing, let it use book
6146         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6147         first.maybeThinking = TRUE;
6148     } else SendMoveToProgram(forwardMostMove-1, &first);
6149     if (currentMove == cmailOldMove + 1) {
6150       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6151     }
6152   }
6153
6154   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6155
6156   switch (gameMode) {
6157   case EditGame:
6158     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6159     case MT_NONE:
6160     case MT_CHECK:
6161       break;
6162     case MT_CHECKMATE:
6163     case MT_STAINMATE:
6164       if (WhiteOnMove(currentMove)) {
6165         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6166       } else {
6167         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6168       }
6169       break;
6170     case MT_STALEMATE:
6171       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6172       break;
6173     }
6174     break;
6175     
6176   case MachinePlaysBlack:
6177   case MachinePlaysWhite:
6178     /* disable certain menu options while machine is thinking */
6179     SetMachineThinkingEnables();
6180     break;
6181
6182   default:
6183     break;
6184   }
6185
6186   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6187         
6188   if(bookHit) { // [HGM] book: simulate book reply
6189         static char bookMove[MSG_SIZ]; // a bit generous?
6190
6191         programStats.nodes = programStats.depth = programStats.time = 
6192         programStats.score = programStats.got_only_move = 0;
6193         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6194
6195         strcpy(bookMove, "move ");
6196         strcat(bookMove, bookHit);
6197         HandleMachineMove(bookMove, &first);
6198   }
6199   return 1;
6200 }
6201
6202 void
6203 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6204      int fromX, fromY, toX, toY;
6205      int promoChar;
6206 {
6207     /* [HGM] This routine was added to allow calling of its two logical
6208        parts from other modules in the old way. Before, UserMoveEvent()
6209        automatically called FinishMove() if the move was OK, and returned
6210        otherwise. I separated the two, in order to make it possible to
6211        slip a promotion popup in between. But that it always needs two
6212        calls, to the first part, (now called UserMoveTest() ), and to
6213        FinishMove if the first part succeeded. Calls that do not need
6214        to do anything in between, can call this routine the old way. 
6215     */
6216     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6217 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6218     if(moveType == AmbiguousMove)
6219         DrawPosition(FALSE, boards[currentMove]);
6220     else if(moveType != ImpossibleMove && moveType != Comment)
6221         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6222 }
6223
6224 void
6225 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6226      Board board;
6227      int flags;
6228      ChessMove kind;
6229      int rf, ff, rt, ft;
6230      VOIDSTAR closure;
6231 {
6232     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6233     Markers *m = (Markers *) closure;
6234     if(rf == fromY && ff == fromX)
6235         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6236                          || kind == WhiteCapturesEnPassant
6237                          || kind == BlackCapturesEnPassant);
6238     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6239 }
6240
6241 void
6242 MarkTargetSquares(int clear)
6243 {
6244   int x, y;
6245   if(!appData.markers || !appData.highlightDragging || 
6246      !appData.testLegality || gameMode == EditPosition) return;
6247   if(clear) {
6248     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6249   } else {
6250     int capt = 0;
6251     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6252     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6253       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6254       if(capt)
6255       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6256     }
6257   }
6258   DrawPosition(TRUE, NULL);
6259 }
6260
6261 void LeftClick(ClickType clickType, int xPix, int yPix)
6262 {
6263     int x, y;
6264     Boolean saveAnimate;
6265     static int second = 0, promotionChoice = 0, dragging = 0;
6266     char promoChoice = NULLCHAR;
6267
6268     if(appData.seekGraph && appData.icsActive && loggedOn &&
6269         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6270         SeekGraphClick(clickType, xPix, yPix, 0);
6271         return;
6272     }
6273
6274     if (clickType == Press) ErrorPopDown();
6275     MarkTargetSquares(1);
6276
6277     x = EventToSquare(xPix, BOARD_WIDTH);
6278     y = EventToSquare(yPix, BOARD_HEIGHT);
6279     if (!flipView && y >= 0) {
6280         y = BOARD_HEIGHT - 1 - y;
6281     }
6282     if (flipView && x >= 0) {
6283         x = BOARD_WIDTH - 1 - x;
6284     }
6285
6286     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6287         if(clickType == Release) return; // ignore upclick of click-click destination
6288         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6289         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6290         if(gameInfo.holdingsWidth && 
6291                 (WhiteOnMove(currentMove) 
6292                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6293                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6294             // click in right holdings, for determining promotion piece
6295             ChessSquare p = boards[currentMove][y][x];
6296             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6297             if(p != EmptySquare) {
6298                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6299                 fromX = fromY = -1;
6300                 return;
6301             }
6302         }
6303         DrawPosition(FALSE, boards[currentMove]);
6304         return;
6305     }
6306
6307     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6308     if(clickType == Press
6309             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6310               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6311               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6312         return;
6313
6314     autoQueen = appData.alwaysPromoteToQueen;
6315
6316     if (fromX == -1) {
6317       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6318         if (clickType == Press) {
6319             /* First square */
6320             if (OKToStartUserMove(x, y)) {
6321                 fromX = x;
6322                 fromY = y;
6323                 second = 0;
6324                 MarkTargetSquares(0);
6325                 DragPieceBegin(xPix, yPix); dragging = 1;
6326                 if (appData.highlightDragging) {
6327                     SetHighlights(x, y, -1, -1);
6328                 }
6329             }
6330         } else if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6331             DragPieceEnd(xPix, yPix); dragging = 0;
6332             DrawPosition(FALSE, NULL);
6333         }
6334         return;
6335       }
6336     }
6337
6338     /* fromX != -1 */
6339     if (clickType == Press && gameMode != EditPosition) {
6340         ChessSquare fromP;
6341         ChessSquare toP;
6342         int frc;
6343
6344         // ignore off-board to clicks
6345         if(y < 0 || x < 0) return;
6346
6347         /* Check if clicking again on the same color piece */
6348         fromP = boards[currentMove][fromY][fromX];
6349         toP = boards[currentMove][y][x];
6350         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6351         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6352              WhitePawn <= toP && toP <= WhiteKing &&
6353              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6354              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6355             (BlackPawn <= fromP && fromP <= BlackKing && 
6356              BlackPawn <= toP && toP <= BlackKing &&
6357              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6358              !(fromP == BlackKing && toP == BlackRook && frc))) {
6359             /* Clicked again on same color piece -- changed his mind */
6360             second = (x == fromX && y == fromY);
6361            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6362             if (appData.highlightDragging) {
6363                 SetHighlights(x, y, -1, -1);
6364             } else {
6365                 ClearHighlights();
6366             }
6367             if (OKToStartUserMove(x, y)) {
6368                 fromX = x;
6369                 fromY = y; dragging = 1;
6370                 MarkTargetSquares(0);
6371                 DragPieceBegin(xPix, yPix);
6372             }
6373             return;
6374            }
6375         }
6376         // ignore clicks on holdings
6377         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6378     }
6379
6380     if (clickType == Release && x == fromX && y == fromY) {
6381         DragPieceEnd(xPix, yPix); dragging = 0;
6382         if (appData.animateDragging) {
6383             /* Undo animation damage if any */
6384             DrawPosition(FALSE, NULL);
6385         }
6386         if (second) {
6387             /* Second up/down in same square; just abort move */
6388             second = 0;
6389             fromX = fromY = -1;
6390             ClearHighlights();
6391             gotPremove = 0;
6392             ClearPremoveHighlights();
6393         } else {
6394             /* First upclick in same square; start click-click mode */
6395             SetHighlights(x, y, -1, -1);
6396         }
6397         return;
6398     }
6399
6400     /* we now have a different from- and (possibly off-board) to-square */
6401     /* Completed move */
6402     toX = x;
6403     toY = y;
6404     saveAnimate = appData.animate;
6405     if (clickType == Press) {
6406         /* Finish clickclick move */
6407         if (appData.animate || appData.highlightLastMove) {
6408             SetHighlights(fromX, fromY, toX, toY);
6409         } else {
6410             ClearHighlights();
6411         }
6412     } else {
6413         /* Finish drag move */
6414         if (appData.highlightLastMove) {
6415             SetHighlights(fromX, fromY, toX, toY);
6416         } else {
6417             ClearHighlights();
6418         }
6419         DragPieceEnd(xPix, yPix); dragging = 0;
6420         /* Don't animate move and drag both */
6421         appData.animate = FALSE;
6422     }
6423
6424     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6425     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6426         ChessSquare piece = boards[currentMove][fromY][fromX];
6427         if(gameMode == EditPosition && piece != EmptySquare &&
6428            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6429             int n;
6430              
6431             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6432                 n = PieceToNumber(piece - (int)BlackPawn);
6433                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6434                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6435                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6436             } else
6437             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6438                 n = PieceToNumber(piece);
6439                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6440                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6441                 boards[currentMove][n][BOARD_WIDTH-2]++;
6442             }
6443             boards[currentMove][fromY][fromX] = EmptySquare;
6444         }
6445         ClearHighlights();
6446         fromX = fromY = -1;
6447         DrawPosition(TRUE, boards[currentMove]);
6448         return;
6449     }
6450
6451     // off-board moves should not be highlighted
6452     if(x < 0 || x < 0) ClearHighlights();
6453
6454     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6455         SetHighlights(fromX, fromY, toX, toY);
6456         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6457             // [HGM] super: promotion to captured piece selected from holdings
6458             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6459             promotionChoice = TRUE;
6460             // kludge follows to temporarily execute move on display, without promoting yet
6461             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6462             boards[currentMove][toY][toX] = p;
6463             DrawPosition(FALSE, boards[currentMove]);
6464             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6465             boards[currentMove][toY][toX] = q;
6466             DisplayMessage("Click in holdings to choose piece", "");
6467             return;
6468         }
6469         PromotionPopUp();
6470     } else {
6471         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6472         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6473         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6474         fromX = fromY = -1;
6475     }
6476     appData.animate = saveAnimate;
6477     if (appData.animate || appData.animateDragging) {
6478         /* Undo animation damage if needed */
6479         DrawPosition(FALSE, NULL);
6480     }
6481 }
6482
6483 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6484 {   // front-end-free part taken out of PieceMenuPopup
6485     int whichMenu; int xSqr, ySqr;
6486
6487     if(seekGraphUp) { // [HGM] seekgraph
6488         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6489         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6490         return -2;
6491     }
6492
6493     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6494          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6495         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6496         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6497         if(action == Press)   {
6498             originalFlip = flipView;
6499             flipView = !flipView; // temporarily flip board to see game from partners perspective
6500             DrawPosition(TRUE, partnerBoard);
6501             DisplayMessage(partnerStatus, "");
6502             partnerUp = TRUE;
6503         } else if(action == Release) {
6504             flipView = originalFlip;
6505             DrawPosition(TRUE, boards[currentMove]);
6506             partnerUp = FALSE;
6507         }
6508         return -2;
6509     }
6510
6511     xSqr = EventToSquare(x, BOARD_WIDTH);
6512     ySqr = EventToSquare(y, BOARD_HEIGHT);
6513     if (action == Release) UnLoadPV(); // [HGM] pv
6514     if (action != Press) return -2; // return code to be ignored
6515     switch (gameMode) {
6516       case IcsExamining:
6517         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6518       case EditPosition:
6519         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6520         if (xSqr < 0 || ySqr < 0) return -1;\r
6521         whichMenu = 0; // edit-position menu
6522         break;
6523       case IcsObserving:
6524         if(!appData.icsEngineAnalyze) return -1;
6525       case IcsPlayingWhite:
6526       case IcsPlayingBlack:
6527         if(!appData.zippyPlay) goto noZip;
6528       case AnalyzeMode:
6529       case AnalyzeFile:
6530       case MachinePlaysWhite:
6531       case MachinePlaysBlack:
6532       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6533         if (!appData.dropMenu) {
6534           LoadPV(x, y);
6535           return 2; // flag front-end to grab mouse events
6536         }
6537         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6538            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6539       case EditGame:
6540       noZip:
6541         if (xSqr < 0 || ySqr < 0) return -1;
6542         if (!appData.dropMenu || appData.testLegality &&
6543             gameInfo.variant != VariantBughouse &&
6544             gameInfo.variant != VariantCrazyhouse) return -1;
6545         whichMenu = 1; // drop menu
6546         break;
6547       default:
6548         return -1;
6549     }
6550
6551     if (((*fromX = xSqr) < 0) ||
6552         ((*fromY = ySqr) < 0)) {
6553         *fromX = *fromY = -1;
6554         return -1;
6555     }
6556     if (flipView)
6557       *fromX = BOARD_WIDTH - 1 - *fromX;
6558     else
6559       *fromY = BOARD_HEIGHT - 1 - *fromY;
6560
6561     return whichMenu;
6562 }
6563
6564 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6565 {
6566 //    char * hint = lastHint;
6567     FrontEndProgramStats stats;
6568
6569     stats.which = cps == &first ? 0 : 1;
6570     stats.depth = cpstats->depth;
6571     stats.nodes = cpstats->nodes;
6572     stats.score = cpstats->score;
6573     stats.time = cpstats->time;
6574     stats.pv = cpstats->movelist;
6575     stats.hint = lastHint;
6576     stats.an_move_index = 0;
6577     stats.an_move_count = 0;
6578
6579     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6580         stats.hint = cpstats->move_name;
6581         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6582         stats.an_move_count = cpstats->nr_moves;
6583     }
6584
6585     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6586
6587     SetProgramStats( &stats );
6588 }
6589
6590 void
6591 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6592 {       // count all piece types
6593         int p, f, r;
6594         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6595         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6596         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6597                 p = board[r][f];
6598                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6599                 if(p == BlackPawn && r == 0) (*bStale)++; else pCnt[p]++; // count last-Rank Pawns (XQ) separately
6600                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6601                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6602                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6603                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6604         }
6605 }
6606
6607 int
6608 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6609 {
6610         VariantClass v = gameInfo.variant;
6611
6612         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6613         if(v == VariantShatranj) return TRUE; // always winnable through baring
6614         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6615         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6616
6617         if(v == VariantXiangqi) {
6618                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6619
6620                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6621                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6622                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6623                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6624                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6625                 if(stale) // we have at least one last-rank P plus perhaps C
6626                     return majors // KPKX
6627                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6628                 else // KCA*E*
6629                     return pCnt[WhiteFerz+side] // KCAK
6630                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6631                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6632                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6633
6634         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6635                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6636                 
6637                 if(nMine == 1) return FALSE; // bare King
6638                 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
6639                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6640                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6641                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6642                 if(pCnt[WhiteKnight+side])
6643                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] + 
6644                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6645                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6646                 if(nBishops)
6647                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6648                 if(pCnt[WhiteAlfil+side])
6649                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6650                 if(pCnt[WhiteWazir+side])
6651                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6652         }
6653
6654         return TRUE;
6655 }
6656
6657 int
6658 Adjudicate(ChessProgramState *cps)
6659 {       // [HGM] some adjudications useful with buggy engines
6660         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6661         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6662         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6663         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6664         int k, count = 0; static int bare = 1;
6665         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6666         Boolean canAdjudicate = !appData.icsActive;
6667
6668         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6669         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6670             if( appData.testLegality )
6671             {   /* [HGM] Some more adjudications for obstinate engines */
6672                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6673                 static int moveCount = 6;
6674                 ChessMove result;
6675                 char *reason = NULL;
6676
6677                 /* Count what is on board. */
6678                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6679
6680                 /* Some material-based adjudications that have to be made before stalemate test */
6681                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6682                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6683                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6684                      if(canAdjudicate && appData.checkMates) {
6685                          if(engineOpponent)
6686                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6687                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6688                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6689                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6690                          return 1;
6691                      }
6692                 }
6693
6694                 /* Bare King in Shatranj (loses) or Losers (wins) */
6695                 if( nrW == 1 || nrB == 1) {
6696                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6697                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6698                      if(canAdjudicate && appData.checkMates) {
6699                          if(engineOpponent)
6700                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6701                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6702                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6703                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6704                          return 1;
6705                      }
6706                   } else
6707                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6708                   {    /* bare King */
6709                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6710                         if(canAdjudicate && appData.checkMates) {
6711                             /* but only adjudicate if adjudication enabled */
6712                             if(engineOpponent)
6713                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6714                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6715                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn, 
6716                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6717                             return 1;
6718                         }
6719                   }
6720                 } else bare = 1;
6721
6722
6723             // don't wait for engine to announce game end if we can judge ourselves
6724             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6725               case MT_CHECK:
6726                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6727                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6728                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6729                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6730                             checkCnt++;
6731                         if(checkCnt >= 2) {
6732                             reason = "Xboard adjudication: 3rd check";
6733                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6734                             break;
6735                         }
6736                     }
6737                 }
6738               case MT_NONE:
6739               default:
6740                 break;
6741               case MT_STALEMATE:
6742               case MT_STAINMATE:
6743                 reason = "Xboard adjudication: Stalemate";
6744                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6745                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6746                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6747                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6748                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6749                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6750                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6751                                                                         EP_CHECKMATE : EP_WINS);
6752                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6753                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6754                 }
6755                 break;
6756               case MT_CHECKMATE:
6757                 reason = "Xboard adjudication: Checkmate";
6758                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6759                 break;
6760             }
6761
6762                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6763                     case EP_STALEMATE:
6764                         result = GameIsDrawn; break;
6765                     case EP_CHECKMATE:
6766                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6767                     case EP_WINS:
6768                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6769                     default:
6770                         result = (ChessMove) 0;
6771                 }
6772                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6773                     if(engineOpponent)
6774                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6775                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6776                     GameEnds( result, reason, GE_XBOARD );
6777                     return 1;
6778                 }
6779
6780                 /* Next absolutely insufficient mating material. */
6781                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6782                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6783                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
6784
6785                      /* always flag draws, for judging claims */
6786                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6787
6788                      if(canAdjudicate && appData.materialDraws) {
6789                          /* but only adjudicate them if adjudication enabled */
6790                          if(engineOpponent) {
6791                            SendToProgram("force\n", engineOpponent); // suppress reply
6792                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6793                          }
6794                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6795                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6796                          return 1;
6797                      }
6798                 }
6799
6800                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6801                 if(nrW + nrB == 4 && 
6802                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6803                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
6804                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
6805                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6806                   ) ) {
6807                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6808                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6809                           if(engineOpponent) {
6810                             SendToProgram("force\n", engineOpponent); // suppress reply
6811                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6812                           }
6813                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6814                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6815                           return 1;
6816                      }
6817                 } else moveCount = 6;
6818             }
6819         }
6820           
6821         if (appData.debugMode) { int i;
6822             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6823                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6824                     appData.drawRepeats);
6825             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6826               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6827             
6828         }
6829
6830         // Repetition draws and 50-move rule can be applied independently of legality testing
6831
6832                 /* Check for rep-draws */
6833                 count = 0;
6834                 for(k = forwardMostMove-2;
6835                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6836                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6837                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6838                     k-=2)
6839                 {   int rights=0;
6840                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6841                         /* compare castling rights */
6842                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6843                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6844                                 rights++; /* King lost rights, while rook still had them */
6845                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6846                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6847                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6848                                    rights++; /* but at least one rook lost them */
6849                         }
6850                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6851                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6852                                 rights++; 
6853                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6854                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6855                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6856                                    rights++;
6857                         }
6858                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6859                             && appData.drawRepeats > 1) {
6860                              /* adjudicate after user-specified nr of repeats */
6861                              int result = GameIsDrawn;
6862                              char *details = "XBoard adjudication: repetition draw";
6863                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6864                                 // [HGM] xiangqi: check for forbidden perpetuals
6865                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6866                                 for(m=forwardMostMove; m>k; m-=2) {
6867                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6868                                         ourPerpetual = 0; // the current mover did not always check
6869                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6870                                         hisPerpetual = 0; // the opponent did not always check
6871                                 }
6872                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6873                                                                         ourPerpetual, hisPerpetual);
6874                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6875                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6876                                     details = "Xboard adjudication: perpetual checking";
6877                                 } else
6878                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
6879                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6880                                 } else
6881                                 // Now check for perpetual chases
6882                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6883                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6884                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6885                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6886                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6887                                         details = "Xboard adjudication: perpetual chasing";
6888                                     } else
6889                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6890                                         break; // Abort repetition-checking loop.
6891                                 }
6892                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6893                              }
6894                              if(engineOpponent) {
6895                                SendToProgram("force\n", engineOpponent); // suppress reply
6896                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6897                              }
6898                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6899                              GameEnds( result, details, GE_XBOARD );
6900                              return 1;
6901                         }
6902                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6903                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6904                     }
6905                 }
6906
6907                 /* Now we test for 50-move draws. Determine ply count */
6908                 count = forwardMostMove;
6909                 /* look for last irreversble move */
6910                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6911                     count--;
6912                 /* if we hit starting position, add initial plies */
6913                 if( count == backwardMostMove )
6914                     count -= initialRulePlies;
6915                 count = forwardMostMove - count; 
6916                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
6917                         // adjust reversible move counter for checks in Xiangqi
6918                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
6919                         if(i < backwardMostMove) i = backwardMostMove;
6920                         while(i <= forwardMostMove) {
6921                                 lastCheck = inCheck; // check evasion does not count
6922                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
6923                                 if(inCheck || lastCheck) count--; // check does not count
6924                                 i++;
6925                         }
6926                 }
6927                 if( count >= 100)
6928                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6929                          /* this is used to judge if draw claims are legal */
6930                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6931                          if(engineOpponent) {
6932                            SendToProgram("force\n", engineOpponent); // suppress reply
6933                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6934                          }
6935                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6936                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6937                          return 1;
6938                 }
6939
6940                 /* if draw offer is pending, treat it as a draw claim
6941                  * when draw condition present, to allow engines a way to
6942                  * claim draws before making their move to avoid a race
6943                  * condition occurring after their move
6944                  */
6945                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6946                          char *p = NULL;
6947                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6948                              p = "Draw claim: 50-move rule";
6949                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6950                              p = "Draw claim: 3-fold repetition";
6951                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6952                              p = "Draw claim: insufficient mating material";
6953                          if( p != NULL && canAdjudicate) {
6954                              if(engineOpponent) {
6955                                SendToProgram("force\n", engineOpponent); // suppress reply
6956                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6957                              }
6958                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6959                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6960                              return 1;
6961                          }
6962                 }
6963
6964                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6965                     if(engineOpponent) {
6966                       SendToProgram("force\n", engineOpponent); // suppress reply
6967                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6968                     }
6969                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6970                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6971                     return 1;
6972                 }
6973         return 0;
6974 }
6975
6976 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6977 {   // [HGM] book: this routine intercepts moves to simulate book replies
6978     char *bookHit = NULL;
6979
6980     //first determine if the incoming move brings opponent into his book
6981     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6982         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6983     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6984     if(bookHit != NULL && !cps->bookSuspend) {
6985         // make sure opponent is not going to reply after receiving move to book position
6986         SendToProgram("force\n", cps);
6987         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6988     }
6989     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6990     // now arrange restart after book miss
6991     if(bookHit) {
6992         // after a book hit we never send 'go', and the code after the call to this routine
6993         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6994         char buf[MSG_SIZ];
6995         sprintf(buf, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
6996         SendToProgram(buf, cps);
6997         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6998     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6999         SendToProgram("go\n", cps);
7000         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7001     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7002         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7003             SendToProgram("go\n", cps); 
7004         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7005     }
7006     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7007 }
7008
7009 char *savedMessage;
7010 ChessProgramState *savedState;
7011 void DeferredBookMove(void)
7012 {
7013         if(savedState->lastPing != savedState->lastPong)
7014                     ScheduleDelayedEvent(DeferredBookMove, 10);
7015         else
7016         HandleMachineMove(savedMessage, savedState);
7017 }
7018
7019 void
7020 HandleMachineMove(message, cps)
7021      char *message;
7022      ChessProgramState *cps;
7023 {
7024     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7025     char realname[MSG_SIZ];
7026     int fromX, fromY, toX, toY;
7027     ChessMove moveType;
7028     char promoChar;
7029     char *p;
7030     int machineWhite;
7031     char *bookHit;
7032
7033     cps->userError = 0;
7034
7035 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7036     /*
7037      * Kludge to ignore BEL characters
7038      */
7039     while (*message == '\007') message++;
7040
7041     /*
7042      * [HGM] engine debug message: ignore lines starting with '#' character
7043      */
7044     if(cps->debug && *message == '#') return;
7045
7046     /*
7047      * Look for book output
7048      */
7049     if (cps == &first && bookRequested) {
7050         if (message[0] == '\t' || message[0] == ' ') {
7051             /* Part of the book output is here; append it */
7052             strcat(bookOutput, message);
7053             strcat(bookOutput, "  \n");
7054             return;
7055         } else if (bookOutput[0] != NULLCHAR) {
7056             /* All of book output has arrived; display it */
7057             char *p = bookOutput;
7058             while (*p != NULLCHAR) {
7059                 if (*p == '\t') *p = ' ';
7060                 p++;
7061             }
7062             DisplayInformation(bookOutput);
7063             bookRequested = FALSE;
7064             /* Fall through to parse the current output */
7065         }
7066     }
7067
7068     /*
7069      * Look for machine move.
7070      */
7071     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7072         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
7073     {
7074         /* This method is only useful on engines that support ping */
7075         if (cps->lastPing != cps->lastPong) {
7076           if (gameMode == BeginningOfGame) {
7077             /* Extra move from before last new; ignore */
7078             if (appData.debugMode) {
7079                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7080             }
7081           } else {
7082             if (appData.debugMode) {
7083                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7084                         cps->which, gameMode);
7085             }
7086
7087             SendToProgram("undo\n", cps);
7088           }
7089           return;
7090         }
7091
7092         switch (gameMode) {
7093           case BeginningOfGame:
7094             /* Extra move from before last reset; ignore */
7095             if (appData.debugMode) {
7096                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7097             }
7098             return;
7099
7100           case EndOfGame:
7101           case IcsIdle:
7102           default:
7103             /* Extra move after we tried to stop.  The mode test is
7104                not a reliable way of detecting this problem, but it's
7105                the best we can do on engines that don't support ping.
7106             */
7107             if (appData.debugMode) {
7108                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7109                         cps->which, gameMode);
7110             }
7111             SendToProgram("undo\n", cps);
7112             return;
7113
7114           case MachinePlaysWhite:
7115           case IcsPlayingWhite:
7116             machineWhite = TRUE;
7117             break;
7118
7119           case MachinePlaysBlack:
7120           case IcsPlayingBlack:
7121             machineWhite = FALSE;
7122             break;
7123
7124           case TwoMachinesPlay:
7125             machineWhite = (cps->twoMachinesColor[0] == 'w');
7126             break;
7127         }
7128         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7129             if (appData.debugMode) {
7130                 fprintf(debugFP,
7131                         "Ignoring move out of turn by %s, gameMode %d"
7132                         ", forwardMost %d\n",
7133                         cps->which, gameMode, forwardMostMove);
7134             }
7135             return;
7136         }
7137
7138     if (appData.debugMode) { int f = forwardMostMove;
7139         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7140                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7141                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7142     }
7143         if(cps->alphaRank) AlphaRank(machineMove, 4);
7144         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7145                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7146             /* Machine move could not be parsed; ignore it. */
7147             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7148                     machineMove, cps->which);
7149             DisplayError(buf1, 0);
7150             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7151                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7152             if (gameMode == TwoMachinesPlay) {
7153               GameEnds(machineWhite ? BlackWins : WhiteWins,
7154                        buf1, GE_XBOARD);
7155             }
7156             return;
7157         }
7158
7159         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7160         /* So we have to redo legality test with true e.p. status here,  */
7161         /* to make sure an illegal e.p. capture does not slip through,   */
7162         /* to cause a forfeit on a justified illegal-move complaint      */
7163         /* of the opponent.                                              */
7164         if( gameMode==TwoMachinesPlay && appData.testLegality
7165             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
7166                                                               ) {
7167            ChessMove moveType;
7168            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7169                              fromY, fromX, toY, toX, promoChar);
7170             if (appData.debugMode) {
7171                 int i;
7172                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7173                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7174                 fprintf(debugFP, "castling rights\n");
7175             }
7176             if(moveType == IllegalMove) {
7177                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7178                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7179                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7180                            buf1, GE_XBOARD);
7181                 return;
7182            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7183            /* [HGM] Kludge to handle engines that send FRC-style castling
7184               when they shouldn't (like TSCP-Gothic) */
7185            switch(moveType) {
7186              case WhiteASideCastleFR:
7187              case BlackASideCastleFR:
7188                toX+=2;
7189                currentMoveString[2]++;
7190                break;
7191              case WhiteHSideCastleFR:
7192              case BlackHSideCastleFR:
7193                toX--;
7194                currentMoveString[2]--;
7195                break;
7196              default: ; // nothing to do, but suppresses warning of pedantic compilers
7197            }
7198         }
7199         hintRequested = FALSE;
7200         lastHint[0] = NULLCHAR;
7201         bookRequested = FALSE;
7202         /* Program may be pondering now */
7203         cps->maybeThinking = TRUE;
7204         if (cps->sendTime == 2) cps->sendTime = 1;
7205         if (cps->offeredDraw) cps->offeredDraw--;
7206
7207         /* currentMoveString is set as a side-effect of ParseOneMove */
7208         strcpy(machineMove, currentMoveString);
7209         strcat(machineMove, "\n");
7210         strcpy(moveList[forwardMostMove], machineMove);
7211
7212         /* [AS] Save move info*/
7213         pvInfoList[ forwardMostMove ].score = programStats.score;
7214         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7215         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7216
7217         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7218
7219         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7220         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7221             int count = 0;
7222
7223             while( count < adjudicateLossPlies ) {
7224                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7225
7226                 if( count & 1 ) {
7227                     score = -score; /* Flip score for winning side */
7228                 }
7229
7230                 if( score > adjudicateLossThreshold ) {
7231                     break;
7232                 }
7233
7234                 count++;
7235             }
7236
7237             if( count >= adjudicateLossPlies ) {
7238                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7239
7240                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7241                     "Xboard adjudication", 
7242                     GE_XBOARD );
7243
7244                 return;
7245             }
7246         }
7247
7248         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7249
7250 #if ZIPPY
7251         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7252             first.initDone) {
7253           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7254                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7255                 SendToICS("draw ");
7256                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7257           }
7258           SendMoveToICS(moveType, fromX, fromY, toX, toY);
7259           ics_user_moved = 1;
7260           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7261                 char buf[3*MSG_SIZ];
7262
7263                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7264                         programStats.score / 100.,
7265                         programStats.depth,
7266                         programStats.time / 100.,
7267                         (unsigned int)programStats.nodes,
7268                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7269                         programStats.movelist);
7270                 SendToICS(buf);
7271 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7272           }
7273         }
7274 #endif
7275
7276         /* [AS] Clear stats for next move */
7277         ClearProgramStats();
7278         thinkOutput[0] = NULLCHAR;
7279         hiddenThinkOutputState = 0;
7280
7281         bookHit = NULL;
7282         if (gameMode == TwoMachinesPlay) {
7283             /* [HGM] relaying draw offers moved to after reception of move */
7284             /* and interpreting offer as claim if it brings draw condition */
7285             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7286                 SendToProgram("draw\n", cps->other);
7287             }
7288             if (cps->other->sendTime) {
7289                 SendTimeRemaining(cps->other,
7290                                   cps->other->twoMachinesColor[0] == 'w');
7291             }
7292             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7293             if (firstMove && !bookHit) {
7294                 firstMove = FALSE;
7295                 if (cps->other->useColors) {
7296                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7297                 }
7298                 SendToProgram("go\n", cps->other);
7299             }
7300             cps->other->maybeThinking = TRUE;
7301         }
7302
7303         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7304         
7305         if (!pausing && appData.ringBellAfterMoves) {
7306             RingBell();
7307         }
7308
7309         /* 
7310          * Reenable menu items that were disabled while
7311          * machine was thinking
7312          */
7313         if (gameMode != TwoMachinesPlay)
7314             SetUserThinkingEnables();
7315
7316         // [HGM] book: after book hit opponent has received move and is now in force mode
7317         // force the book reply into it, and then fake that it outputted this move by jumping
7318         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7319         if(bookHit) {
7320                 static char bookMove[MSG_SIZ]; // a bit generous?
7321
7322                 strcpy(bookMove, "move ");
7323                 strcat(bookMove, bookHit);
7324                 message = bookMove;
7325                 cps = cps->other;
7326                 programStats.nodes = programStats.depth = programStats.time = 
7327                 programStats.score = programStats.got_only_move = 0;
7328                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7329
7330                 if(cps->lastPing != cps->lastPong) {
7331                     savedMessage = message; // args for deferred call
7332                     savedState = cps;
7333                     ScheduleDelayedEvent(DeferredBookMove, 10);
7334                     return;
7335                 }
7336                 goto FakeBookMove;
7337         }
7338
7339         return;
7340     }
7341
7342     /* Set special modes for chess engines.  Later something general
7343      *  could be added here; for now there is just one kludge feature,
7344      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7345      *  when "xboard" is given as an interactive command.
7346      */
7347     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7348         cps->useSigint = FALSE;
7349         cps->useSigterm = FALSE;
7350     }
7351     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7352       ParseFeatures(message+8, cps);
7353       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7354     }
7355
7356     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7357      * want this, I was asked to put it in, and obliged.
7358      */
7359     if (!strncmp(message, "setboard ", 9)) {
7360         Board initial_position;
7361
7362         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7363
7364         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7365             DisplayError(_("Bad FEN received from engine"), 0);
7366             return ;
7367         } else {
7368            Reset(TRUE, FALSE);
7369            CopyBoard(boards[0], initial_position);
7370            initialRulePlies = FENrulePlies;
7371            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7372            else gameMode = MachinePlaysBlack;                 
7373            DrawPosition(FALSE, boards[currentMove]);
7374         }
7375         return;
7376     }
7377
7378     /*
7379      * Look for communication commands
7380      */
7381     if (!strncmp(message, "telluser ", 9)) {
7382         EscapeExpand(message+9, message+9); // [HGM] esc: allow escape sequences in popup box
7383         DisplayNote(message + 9);
7384         return;
7385     }
7386     if (!strncmp(message, "tellusererror ", 14)) {
7387         cps->userError = 1;
7388         EscapeExpand(message+14, message+14); // [HGM] esc: allow escape sequences in popup box
7389         DisplayError(message + 14, 0);
7390         return;
7391     }
7392     if (!strncmp(message, "tellopponent ", 13)) {
7393       if (appData.icsActive) {
7394         if (loggedOn) {
7395           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7396           SendToICS(buf1);
7397         }
7398       } else {
7399         DisplayNote(message + 13);
7400       }
7401       return;
7402     }
7403     if (!strncmp(message, "tellothers ", 11)) {
7404       if (appData.icsActive) {
7405         if (loggedOn) {
7406           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7407           SendToICS(buf1);
7408         }
7409       }
7410       return;
7411     }
7412     if (!strncmp(message, "tellall ", 8)) {
7413       if (appData.icsActive) {
7414         if (loggedOn) {
7415           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7416           SendToICS(buf1);
7417         }
7418       } else {
7419         DisplayNote(message + 8);
7420       }
7421       return;
7422     }
7423     if (strncmp(message, "warning", 7) == 0) {
7424         /* Undocumented feature, use tellusererror in new code */
7425         DisplayError(message, 0);
7426         return;
7427     }
7428     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7429         strcpy(realname, cps->tidy);
7430         strcat(realname, " query");
7431         AskQuestion(realname, buf2, buf1, cps->pr);
7432         return;
7433     }
7434     /* Commands from the engine directly to ICS.  We don't allow these to be 
7435      *  sent until we are logged on. Crafty kibitzes have been known to 
7436      *  interfere with the login process.
7437      */
7438     if (loggedOn) {
7439         if (!strncmp(message, "tellics ", 8)) {
7440             SendToICS(message + 8);
7441             SendToICS("\n");
7442             return;
7443         }
7444         if (!strncmp(message, "tellicsnoalias ", 15)) {
7445             SendToICS(ics_prefix);
7446             SendToICS(message + 15);
7447             SendToICS("\n");
7448             return;
7449         }
7450         /* The following are for backward compatibility only */
7451         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7452             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7453             SendToICS(ics_prefix);
7454             SendToICS(message);
7455             SendToICS("\n");
7456             return;
7457         }
7458     }
7459     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7460         return;
7461     }
7462     /*
7463      * If the move is illegal, cancel it and redraw the board.
7464      * Also deal with other error cases.  Matching is rather loose
7465      * here to accommodate engines written before the spec.
7466      */
7467     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7468         strncmp(message, "Error", 5) == 0) {
7469         if (StrStr(message, "name") || 
7470             StrStr(message, "rating") || StrStr(message, "?") ||
7471             StrStr(message, "result") || StrStr(message, "board") ||
7472             StrStr(message, "bk") || StrStr(message, "computer") ||
7473             StrStr(message, "variant") || StrStr(message, "hint") ||
7474             StrStr(message, "random") || StrStr(message, "depth") ||
7475             StrStr(message, "accepted")) {
7476             return;
7477         }
7478         if (StrStr(message, "protover")) {
7479           /* Program is responding to input, so it's apparently done
7480              initializing, and this error message indicates it is
7481              protocol version 1.  So we don't need to wait any longer
7482              for it to initialize and send feature commands. */
7483           FeatureDone(cps, 1);
7484           cps->protocolVersion = 1;
7485           return;
7486         }
7487         cps->maybeThinking = FALSE;
7488
7489         if (StrStr(message, "draw")) {
7490             /* Program doesn't have "draw" command */
7491             cps->sendDrawOffers = 0;
7492             return;
7493         }
7494         if (cps->sendTime != 1 &&
7495             (StrStr(message, "time") || StrStr(message, "otim"))) {
7496           /* Program apparently doesn't have "time" or "otim" command */
7497           cps->sendTime = 0;
7498           return;
7499         }
7500         if (StrStr(message, "analyze")) {
7501             cps->analysisSupport = FALSE;
7502             cps->analyzing = FALSE;
7503             Reset(FALSE, TRUE);
7504             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7505             DisplayError(buf2, 0);
7506             return;
7507         }
7508         if (StrStr(message, "(no matching move)st")) {
7509           /* Special kludge for GNU Chess 4 only */
7510           cps->stKludge = TRUE;
7511           SendTimeControl(cps, movesPerSession, timeControl,
7512                           timeIncrement, appData.searchDepth,
7513                           searchTime);
7514           return;
7515         }
7516         if (StrStr(message, "(no matching move)sd")) {
7517           /* Special kludge for GNU Chess 4 only */
7518           cps->sdKludge = TRUE;
7519           SendTimeControl(cps, movesPerSession, timeControl,
7520                           timeIncrement, appData.searchDepth,
7521                           searchTime);
7522           return;
7523         }
7524         if (!StrStr(message, "llegal")) {
7525             return;
7526         }
7527         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7528             gameMode == IcsIdle) return;
7529         if (forwardMostMove <= backwardMostMove) return;
7530         if (pausing) PauseEvent();
7531       if(appData.forceIllegal) {
7532             // [HGM] illegal: machine refused move; force position after move into it
7533           SendToProgram("force\n", cps);
7534           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7535                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7536                 // when black is to move, while there might be nothing on a2 or black
7537                 // might already have the move. So send the board as if white has the move.
7538                 // But first we must change the stm of the engine, as it refused the last move
7539                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7540                 if(WhiteOnMove(forwardMostMove)) {
7541                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7542                     SendBoard(cps, forwardMostMove); // kludgeless board
7543                 } else {
7544                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7545                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7546                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7547                 }
7548           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7549             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7550                  gameMode == TwoMachinesPlay)
7551               SendToProgram("go\n", cps);
7552             return;
7553       } else
7554         if (gameMode == PlayFromGameFile) {
7555             /* Stop reading this game file */
7556             gameMode = EditGame;
7557             ModeHighlight();
7558         }
7559         currentMove = forwardMostMove-1;
7560         DisplayMove(currentMove-1); /* before DisplayMoveError */
7561         SwitchClocks(forwardMostMove-1); // [HGM] race
7562         DisplayBothClocks();
7563         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7564                 parseList[currentMove], cps->which);
7565         DisplayMoveError(buf1);
7566         DrawPosition(FALSE, boards[currentMove]);
7567
7568         /* [HGM] illegal-move claim should forfeit game when Xboard */
7569         /* only passes fully legal moves                            */
7570         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7571             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7572                                 "False illegal-move claim", GE_XBOARD );
7573         }
7574         return;
7575     }
7576     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7577         /* Program has a broken "time" command that
7578            outputs a string not ending in newline.
7579            Don't use it. */
7580         cps->sendTime = 0;
7581     }
7582     
7583     /*
7584      * If chess program startup fails, exit with an error message.
7585      * Attempts to recover here are futile.
7586      */
7587     if ((StrStr(message, "unknown host") != NULL)
7588         || (StrStr(message, "No remote directory") != NULL)
7589         || (StrStr(message, "not found") != NULL)
7590         || (StrStr(message, "No such file") != NULL)
7591         || (StrStr(message, "can't alloc") != NULL)
7592         || (StrStr(message, "Permission denied") != NULL)) {
7593
7594         cps->maybeThinking = FALSE;
7595         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7596                 cps->which, cps->program, cps->host, message);
7597         RemoveInputSource(cps->isr);
7598         DisplayFatalError(buf1, 0, 1);
7599         return;
7600     }
7601     
7602     /* 
7603      * Look for hint output
7604      */
7605     if (sscanf(message, "Hint: %s", buf1) == 1) {
7606         if (cps == &first && hintRequested) {
7607             hintRequested = FALSE;
7608             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7609                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7610                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7611                                     PosFlags(forwardMostMove),
7612                                     fromY, fromX, toY, toX, promoChar, buf1);
7613                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7614                 DisplayInformation(buf2);
7615             } else {
7616                 /* Hint move could not be parsed!? */
7617               snprintf(buf2, sizeof(buf2),
7618                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7619                         buf1, cps->which);
7620                 DisplayError(buf2, 0);
7621             }
7622         } else {
7623             strcpy(lastHint, buf1);
7624         }
7625         return;
7626     }
7627
7628     /*
7629      * Ignore other messages if game is not in progress
7630      */
7631     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7632         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7633
7634     /*
7635      * look for win, lose, draw, or draw offer
7636      */
7637     if (strncmp(message, "1-0", 3) == 0) {
7638         char *p, *q, *r = "";
7639         p = strchr(message, '{');
7640         if (p) {
7641             q = strchr(p, '}');
7642             if (q) {
7643                 *q = NULLCHAR;
7644                 r = p + 1;
7645             }
7646         }
7647         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7648         return;
7649     } else if (strncmp(message, "0-1", 3) == 0) {
7650         char *p, *q, *r = "";
7651         p = strchr(message, '{');
7652         if (p) {
7653             q = strchr(p, '}');
7654             if (q) {
7655                 *q = NULLCHAR;
7656                 r = p + 1;
7657             }
7658         }
7659         /* Kludge for Arasan 4.1 bug */
7660         if (strcmp(r, "Black resigns") == 0) {
7661             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7662             return;
7663         }
7664         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7665         return;
7666     } else if (strncmp(message, "1/2", 3) == 0) {
7667         char *p, *q, *r = "";
7668         p = strchr(message, '{');
7669         if (p) {
7670             q = strchr(p, '}');
7671             if (q) {
7672                 *q = NULLCHAR;
7673                 r = p + 1;
7674             }
7675         }
7676             
7677         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7678         return;
7679
7680     } else if (strncmp(message, "White resign", 12) == 0) {
7681         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7682         return;
7683     } else if (strncmp(message, "Black resign", 12) == 0) {
7684         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7685         return;
7686     } else if (strncmp(message, "White matches", 13) == 0 ||
7687                strncmp(message, "Black matches", 13) == 0   ) {
7688         /* [HGM] ignore GNUShogi noises */
7689         return;
7690     } else if (strncmp(message, "White", 5) == 0 &&
7691                message[5] != '(' &&
7692                StrStr(message, "Black") == NULL) {
7693         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7694         return;
7695     } else if (strncmp(message, "Black", 5) == 0 &&
7696                message[5] != '(') {
7697         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7698         return;
7699     } else if (strcmp(message, "resign") == 0 ||
7700                strcmp(message, "computer resigns") == 0) {
7701         switch (gameMode) {
7702           case MachinePlaysBlack:
7703           case IcsPlayingBlack:
7704             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7705             break;
7706           case MachinePlaysWhite:
7707           case IcsPlayingWhite:
7708             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7709             break;
7710           case TwoMachinesPlay:
7711             if (cps->twoMachinesColor[0] == 'w')
7712               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7713             else
7714               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7715             break;
7716           default:
7717             /* can't happen */
7718             break;
7719         }
7720         return;
7721     } else if (strncmp(message, "opponent mates", 14) == 0) {
7722         switch (gameMode) {
7723           case MachinePlaysBlack:
7724           case IcsPlayingBlack:
7725             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7726             break;
7727           case MachinePlaysWhite:
7728           case IcsPlayingWhite:
7729             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7730             break;
7731           case TwoMachinesPlay:
7732             if (cps->twoMachinesColor[0] == 'w')
7733               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7734             else
7735               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7736             break;
7737           default:
7738             /* can't happen */
7739             break;
7740         }
7741         return;
7742     } else if (strncmp(message, "computer mates", 14) == 0) {
7743         switch (gameMode) {
7744           case MachinePlaysBlack:
7745           case IcsPlayingBlack:
7746             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7747             break;
7748           case MachinePlaysWhite:
7749           case IcsPlayingWhite:
7750             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7751             break;
7752           case TwoMachinesPlay:
7753             if (cps->twoMachinesColor[0] == 'w')
7754               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7755             else
7756               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7757             break;
7758           default:
7759             /* can't happen */
7760             break;
7761         }
7762         return;
7763     } else if (strncmp(message, "checkmate", 9) == 0) {
7764         if (WhiteOnMove(forwardMostMove)) {
7765             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7766         } else {
7767             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7768         }
7769         return;
7770     } else if (strstr(message, "Draw") != NULL ||
7771                strstr(message, "game is a draw") != NULL) {
7772         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7773         return;
7774     } else if (strstr(message, "offer") != NULL &&
7775                strstr(message, "draw") != NULL) {
7776 #if ZIPPY
7777         if (appData.zippyPlay && first.initDone) {
7778             /* Relay offer to ICS */
7779             SendToICS(ics_prefix);
7780             SendToICS("draw\n");
7781         }
7782 #endif
7783         cps->offeredDraw = 2; /* valid until this engine moves twice */
7784         if (gameMode == TwoMachinesPlay) {
7785             if (cps->other->offeredDraw) {
7786                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7787             /* [HGM] in two-machine mode we delay relaying draw offer      */
7788             /* until after we also have move, to see if it is really claim */
7789             }
7790         } else if (gameMode == MachinePlaysWhite ||
7791                    gameMode == MachinePlaysBlack) {
7792           if (userOfferedDraw) {
7793             DisplayInformation(_("Machine accepts your draw offer"));
7794             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7795           } else {
7796             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7797           }
7798         }
7799     }
7800
7801     
7802     /*
7803      * Look for thinking output
7804      */
7805     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7806           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7807                                 ) {
7808         int plylev, mvleft, mvtot, curscore, time;
7809         char mvname[MOVE_LEN];
7810         u64 nodes; // [DM]
7811         char plyext;
7812         int ignore = FALSE;
7813         int prefixHint = FALSE;
7814         mvname[0] = NULLCHAR;
7815
7816         switch (gameMode) {
7817           case MachinePlaysBlack:
7818           case IcsPlayingBlack:
7819             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7820             break;
7821           case MachinePlaysWhite:
7822           case IcsPlayingWhite:
7823             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7824             break;
7825           case AnalyzeMode:
7826           case AnalyzeFile:
7827             break;
7828           case IcsObserving: /* [DM] icsEngineAnalyze */
7829             if (!appData.icsEngineAnalyze) ignore = TRUE;
7830             break;
7831           case TwoMachinesPlay:
7832             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7833                 ignore = TRUE;
7834             }
7835             break;
7836           default:
7837             ignore = TRUE;
7838             break;
7839         }
7840
7841         if (!ignore) {
7842             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7843             buf1[0] = NULLCHAR;
7844             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7845                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7846
7847                 if (plyext != ' ' && plyext != '\t') {
7848                     time *= 100;
7849                 }
7850
7851                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7852                 if( cps->scoreIsAbsolute && 
7853                     ( gameMode == MachinePlaysBlack ||
7854                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7855                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7856                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7857                      !WhiteOnMove(currentMove)
7858                     ) )
7859                 {
7860                     curscore = -curscore;
7861                 }
7862
7863
7864                 tempStats.depth = plylev;
7865                 tempStats.nodes = nodes;
7866                 tempStats.time = time;
7867                 tempStats.score = curscore;
7868                 tempStats.got_only_move = 0;
7869
7870                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7871                         int ticklen;
7872
7873                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7874                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7875                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7876                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7877                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7878                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7879                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7880                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7881                 }
7882
7883                 /* Buffer overflow protection */
7884                 if (buf1[0] != NULLCHAR) {
7885                     if (strlen(buf1) >= sizeof(tempStats.movelist)
7886                         && appData.debugMode) {
7887                         fprintf(debugFP,
7888                                 "PV is too long; using the first %u bytes.\n",
7889                                 (unsigned) sizeof(tempStats.movelist) - 1);
7890                     }
7891
7892                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist) );
7893                 } else {
7894                     sprintf(tempStats.movelist, " no PV\n");
7895                 }
7896
7897                 if (tempStats.seen_stat) {
7898                     tempStats.ok_to_send = 1;
7899                 }
7900
7901                 if (strchr(tempStats.movelist, '(') != NULL) {
7902                     tempStats.line_is_book = 1;
7903                     tempStats.nr_moves = 0;
7904                     tempStats.moves_left = 0;
7905                 } else {
7906                     tempStats.line_is_book = 0;
7907                 }
7908
7909                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
7910                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
7911
7912                 SendProgramStatsToFrontend( cps, &tempStats );
7913
7914                 /* 
7915                     [AS] Protect the thinkOutput buffer from overflow... this
7916                     is only useful if buf1 hasn't overflowed first!
7917                 */
7918                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7919                         plylev, 
7920                         (gameMode == TwoMachinesPlay ?
7921                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7922                         ((double) curscore) / 100.0,
7923                         prefixHint ? lastHint : "",
7924                         prefixHint ? " " : "" );
7925
7926                 if( buf1[0] != NULLCHAR ) {
7927                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7928
7929                     if( strlen(buf1) > max_len ) {
7930                         if( appData.debugMode) {
7931                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7932                         }
7933                         buf1[max_len+1] = '\0';
7934                     }
7935
7936                     strcat( thinkOutput, buf1 );
7937                 }
7938
7939                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7940                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7941                     DisplayMove(currentMove - 1);
7942                 }
7943                 return;
7944
7945             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7946                 /* crafty (9.25+) says "(only move) <move>"
7947                  * if there is only 1 legal move
7948                  */
7949                 sscanf(p, "(only move) %s", buf1);
7950                 sprintf(thinkOutput, "%s (only move)", buf1);
7951                 sprintf(programStats.movelist, "%s (only move)", buf1);
7952                 programStats.depth = 1;
7953                 programStats.nr_moves = 1;
7954                 programStats.moves_left = 1;
7955                 programStats.nodes = 1;
7956                 programStats.time = 1;
7957                 programStats.got_only_move = 1;
7958
7959                 /* Not really, but we also use this member to
7960                    mean "line isn't going to change" (Crafty
7961                    isn't searching, so stats won't change) */
7962                 programStats.line_is_book = 1;
7963
7964                 SendProgramStatsToFrontend( cps, &programStats );
7965                 
7966                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7967                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7968                     DisplayMove(currentMove - 1);
7969                 }
7970                 return;
7971             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7972                               &time, &nodes, &plylev, &mvleft,
7973                               &mvtot, mvname) >= 5) {
7974                 /* The stat01: line is from Crafty (9.29+) in response
7975                    to the "." command */
7976                 programStats.seen_stat = 1;
7977                 cps->maybeThinking = TRUE;
7978
7979                 if (programStats.got_only_move || !appData.periodicUpdates)
7980                   return;
7981
7982                 programStats.depth = plylev;
7983                 programStats.time = time;
7984                 programStats.nodes = nodes;
7985                 programStats.moves_left = mvleft;
7986                 programStats.nr_moves = mvtot;
7987                 strcpy(programStats.move_name, mvname);
7988                 programStats.ok_to_send = 1;
7989                 programStats.movelist[0] = '\0';
7990
7991                 SendProgramStatsToFrontend( cps, &programStats );
7992
7993                 return;
7994
7995             } else if (strncmp(message,"++",2) == 0) {
7996                 /* Crafty 9.29+ outputs this */
7997                 programStats.got_fail = 2;
7998                 return;
7999
8000             } else if (strncmp(message,"--",2) == 0) {
8001                 /* Crafty 9.29+ outputs this */
8002                 programStats.got_fail = 1;
8003                 return;
8004
8005             } else if (thinkOutput[0] != NULLCHAR &&
8006                        strncmp(message, "    ", 4) == 0) {
8007                 unsigned message_len;
8008
8009                 p = message;
8010                 while (*p && *p == ' ') p++;
8011
8012                 message_len = strlen( p );
8013
8014                 /* [AS] Avoid buffer overflow */
8015                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8016                     strcat(thinkOutput, " ");
8017                     strcat(thinkOutput, p);
8018                 }
8019
8020                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8021                     strcat(programStats.movelist, " ");
8022                     strcat(programStats.movelist, p);
8023                 }
8024
8025                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8026                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8027                     DisplayMove(currentMove - 1);
8028                 }
8029                 return;
8030             }
8031         }
8032         else {
8033             buf1[0] = NULLCHAR;
8034
8035             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8036                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
8037             {
8038                 ChessProgramStats cpstats;
8039
8040                 if (plyext != ' ' && plyext != '\t') {
8041                     time *= 100;
8042                 }
8043
8044                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8045                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8046                     curscore = -curscore;
8047                 }
8048
8049                 cpstats.depth = plylev;
8050                 cpstats.nodes = nodes;
8051                 cpstats.time = time;
8052                 cpstats.score = curscore;
8053                 cpstats.got_only_move = 0;
8054                 cpstats.movelist[0] = '\0';
8055
8056                 if (buf1[0] != NULLCHAR) {
8057                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
8058                 }
8059
8060                 cpstats.ok_to_send = 0;
8061                 cpstats.line_is_book = 0;
8062                 cpstats.nr_moves = 0;
8063                 cpstats.moves_left = 0;
8064
8065                 SendProgramStatsToFrontend( cps, &cpstats );
8066             }
8067         }
8068     }
8069 }
8070
8071
8072 /* Parse a game score from the character string "game", and
8073    record it as the history of the current game.  The game
8074    score is NOT assumed to start from the standard position. 
8075    The display is not updated in any way.
8076    */
8077 void
8078 ParseGameHistory(game)
8079      char *game;
8080 {
8081     ChessMove moveType;
8082     int fromX, fromY, toX, toY, boardIndex;
8083     char promoChar;
8084     char *p, *q;
8085     char buf[MSG_SIZ];
8086
8087     if (appData.debugMode)
8088       fprintf(debugFP, "Parsing game history: %s\n", game);
8089
8090     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8091     gameInfo.site = StrSave(appData.icsHost);
8092     gameInfo.date = PGNDate();
8093     gameInfo.round = StrSave("-");
8094
8095     /* Parse out names of players */
8096     while (*game == ' ') game++;
8097     p = buf;
8098     while (*game != ' ') *p++ = *game++;
8099     *p = NULLCHAR;
8100     gameInfo.white = StrSave(buf);
8101     while (*game == ' ') game++;
8102     p = buf;
8103     while (*game != ' ' && *game != '\n') *p++ = *game++;
8104     *p = NULLCHAR;
8105     gameInfo.black = StrSave(buf);
8106
8107     /* Parse moves */
8108     boardIndex = blackPlaysFirst ? 1 : 0;
8109     yynewstr(game);
8110     for (;;) {
8111         yyboardindex = boardIndex;
8112         moveType = (ChessMove) yylex();
8113         switch (moveType) {
8114           case IllegalMove:             /* maybe suicide chess, etc. */
8115   if (appData.debugMode) {
8116     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8117     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8118     setbuf(debugFP, NULL);
8119   }
8120           case WhitePromotionChancellor:
8121           case BlackPromotionChancellor:
8122           case WhitePromotionArchbishop:
8123           case BlackPromotionArchbishop:
8124           case WhitePromotionQueen:
8125           case BlackPromotionQueen:
8126           case WhitePromotionRook:
8127           case BlackPromotionRook:
8128           case WhitePromotionBishop:
8129           case BlackPromotionBishop:
8130           case WhitePromotionKnight:
8131           case BlackPromotionKnight:
8132           case WhitePromotionKing:
8133           case BlackPromotionKing:
8134           case NormalMove:
8135           case WhiteCapturesEnPassant:
8136           case BlackCapturesEnPassant:
8137           case WhiteKingSideCastle:
8138           case WhiteQueenSideCastle:
8139           case BlackKingSideCastle:
8140           case BlackQueenSideCastle:
8141           case WhiteKingSideCastleWild:
8142           case WhiteQueenSideCastleWild:
8143           case BlackKingSideCastleWild:
8144           case BlackQueenSideCastleWild:
8145           /* PUSH Fabien */
8146           case WhiteHSideCastleFR:
8147           case WhiteASideCastleFR:
8148           case BlackHSideCastleFR:
8149           case BlackASideCastleFR:
8150           /* POP Fabien */
8151             fromX = currentMoveString[0] - AAA;
8152             fromY = currentMoveString[1] - ONE;
8153             toX = currentMoveString[2] - AAA;
8154             toY = currentMoveString[3] - ONE;
8155             promoChar = currentMoveString[4];
8156             break;
8157           case WhiteDrop:
8158           case BlackDrop:
8159             fromX = moveType == WhiteDrop ?
8160               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8161             (int) CharToPiece(ToLower(currentMoveString[0]));
8162             fromY = DROP_RANK;
8163             toX = currentMoveString[2] - AAA;
8164             toY = currentMoveString[3] - ONE;
8165             promoChar = NULLCHAR;
8166             break;
8167           case AmbiguousMove:
8168             /* bug? */
8169             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8170   if (appData.debugMode) {
8171     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8172     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8173     setbuf(debugFP, NULL);
8174   }
8175             DisplayError(buf, 0);
8176             return;
8177           case ImpossibleMove:
8178             /* bug? */
8179             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8180   if (appData.debugMode) {
8181     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8182     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8183     setbuf(debugFP, NULL);
8184   }
8185             DisplayError(buf, 0);
8186             return;
8187           case (ChessMove) 0:   /* end of file */
8188             if (boardIndex < backwardMostMove) {
8189                 /* Oops, gap.  How did that happen? */
8190                 DisplayError(_("Gap in move list"), 0);
8191                 return;
8192             }
8193             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8194             if (boardIndex > forwardMostMove) {
8195                 forwardMostMove = boardIndex;
8196             }
8197             return;
8198           case ElapsedTime:
8199             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8200                 strcat(parseList[boardIndex-1], " ");
8201                 strcat(parseList[boardIndex-1], yy_text);
8202             }
8203             continue;
8204           case Comment:
8205           case PGNTag:
8206           case NAG:
8207           default:
8208             /* ignore */
8209             continue;
8210           case WhiteWins:
8211           case BlackWins:
8212           case GameIsDrawn:
8213           case GameUnfinished:
8214             if (gameMode == IcsExamining) {
8215                 if (boardIndex < backwardMostMove) {
8216                     /* Oops, gap.  How did that happen? */
8217                     return;
8218                 }
8219                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8220                 return;
8221             }
8222             gameInfo.result = moveType;
8223             p = strchr(yy_text, '{');
8224             if (p == NULL) p = strchr(yy_text, '(');
8225             if (p == NULL) {
8226                 p = yy_text;
8227                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8228             } else {
8229                 q = strchr(p, *p == '{' ? '}' : ')');
8230                 if (q != NULL) *q = NULLCHAR;
8231                 p++;
8232             }
8233             gameInfo.resultDetails = StrSave(p);
8234             continue;
8235         }
8236         if (boardIndex >= forwardMostMove &&
8237             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8238             backwardMostMove = blackPlaysFirst ? 1 : 0;
8239             return;
8240         }
8241         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8242                                  fromY, fromX, toY, toX, promoChar,
8243                                  parseList[boardIndex]);
8244         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8245         /* currentMoveString is set as a side-effect of yylex */
8246         strcpy(moveList[boardIndex], currentMoveString);
8247         strcat(moveList[boardIndex], "\n");
8248         boardIndex++;
8249         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8250         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8251           case MT_NONE:
8252           case MT_STALEMATE:
8253           default:
8254             break;
8255           case MT_CHECK:
8256             if(gameInfo.variant != VariantShogi)
8257                 strcat(parseList[boardIndex - 1], "+");
8258             break;
8259           case MT_CHECKMATE:
8260           case MT_STAINMATE:
8261             strcat(parseList[boardIndex - 1], "#");
8262             break;
8263         }
8264     }
8265 }
8266
8267
8268 /* Apply a move to the given board  */
8269 void
8270 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8271      int fromX, fromY, toX, toY;
8272      int promoChar;
8273      Board board;
8274 {
8275   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8276   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8277
8278     /* [HGM] compute & store e.p. status and castling rights for new position */
8279     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8280     { int i;
8281
8282       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8283       oldEP = (signed char)board[EP_STATUS];
8284       board[EP_STATUS] = EP_NONE;
8285
8286       if( board[toY][toX] != EmptySquare ) 
8287            board[EP_STATUS] = EP_CAPTURE;  
8288
8289       if( board[fromY][fromX] == WhitePawn ) {
8290            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8291                board[EP_STATUS] = EP_PAWN_MOVE;
8292            if( toY-fromY==2) {
8293                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8294                         gameInfo.variant != VariantBerolina || toX < fromX)
8295                       board[EP_STATUS] = toX | berolina;
8296                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8297                         gameInfo.variant != VariantBerolina || toX > fromX) 
8298                       board[EP_STATUS] = toX;
8299            }
8300       } else 
8301       if( board[fromY][fromX] == BlackPawn ) {
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] == WhitePawn &&
8306                         gameInfo.variant != VariantBerolina || toX < fromX)
8307                       board[EP_STATUS] = toX | berolina;
8308                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8309                         gameInfo.variant != VariantBerolina || toX > fromX) 
8310                       board[EP_STATUS] = toX;
8311            }
8312        }
8313
8314        for(i=0; i<nrCastlingRights; i++) {
8315            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8316               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8317              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8318        }
8319
8320     }
8321
8322   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8323   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8324        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8325          
8326   if (fromX == toX && fromY == toY) return;
8327
8328   if (fromY == DROP_RANK) {
8329         /* must be first */
8330         piece = board[toY][toX] = (ChessSquare) fromX;
8331   } else {
8332      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8333      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8334      if(gameInfo.variant == VariantKnightmate)
8335          king += (int) WhiteUnicorn - (int) WhiteKing;
8336
8337     /* Code added by Tord: */
8338     /* FRC castling assumed when king captures friendly rook. */
8339     if (board[fromY][fromX] == WhiteKing &&
8340              board[toY][toX] == WhiteRook) {
8341       board[fromY][fromX] = EmptySquare;
8342       board[toY][toX] = EmptySquare;
8343       if(toX > fromX) {
8344         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8345       } else {
8346         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8347       }
8348     } else if (board[fromY][fromX] == BlackKing &&
8349                board[toY][toX] == BlackRook) {
8350       board[fromY][fromX] = EmptySquare;
8351       board[toY][toX] = EmptySquare;
8352       if(toX > fromX) {
8353         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8354       } else {
8355         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8356       }
8357     /* End of code added by Tord */
8358
8359     } else if (board[fromY][fromX] == king
8360         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8361         && toY == fromY && toX > fromX+1) {
8362         board[fromY][fromX] = EmptySquare;
8363         board[toY][toX] = king;
8364         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8365         board[fromY][BOARD_RGHT-1] = EmptySquare;
8366     } else if (board[fromY][fromX] == king
8367         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8368                && toY == fromY && toX < fromX-1) {
8369         board[fromY][fromX] = EmptySquare;
8370         board[toY][toX] = king;
8371         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8372         board[fromY][BOARD_LEFT] = EmptySquare;
8373     } else if (board[fromY][fromX] == WhitePawn
8374                && toY >= BOARD_HEIGHT-promoRank
8375                && gameInfo.variant != VariantXiangqi
8376                ) {
8377         /* white pawn promotion */
8378         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8379         if (board[toY][toX] == EmptySquare) {
8380             board[toY][toX] = WhiteQueen;
8381         }
8382         if(gameInfo.variant==VariantBughouse ||
8383            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8384             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8385         board[fromY][fromX] = EmptySquare;
8386     } else if ((fromY == BOARD_HEIGHT-4)
8387                && (toX != fromX)
8388                && gameInfo.variant != VariantXiangqi
8389                && gameInfo.variant != VariantBerolina
8390                && (board[fromY][fromX] == WhitePawn)
8391                && (board[toY][toX] == EmptySquare)) {
8392         board[fromY][fromX] = EmptySquare;
8393         board[toY][toX] = WhitePawn;
8394         captured = board[toY - 1][toX];
8395         board[toY - 1][toX] = EmptySquare;
8396     } else if ((fromY == BOARD_HEIGHT-4)
8397                && (toX == fromX)
8398                && gameInfo.variant == VariantBerolina
8399                && (board[fromY][fromX] == WhitePawn)
8400                && (board[toY][toX] == EmptySquare)) {
8401         board[fromY][fromX] = EmptySquare;
8402         board[toY][toX] = WhitePawn;
8403         if(oldEP & EP_BEROLIN_A) {
8404                 captured = board[fromY][fromX-1];
8405                 board[fromY][fromX-1] = EmptySquare;
8406         }else{  captured = board[fromY][fromX+1];
8407                 board[fromY][fromX+1] = EmptySquare;
8408         }
8409     } else if (board[fromY][fromX] == king
8410         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8411                && toY == fromY && toX > fromX+1) {
8412         board[fromY][fromX] = EmptySquare;
8413         board[toY][toX] = king;
8414         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8415         board[fromY][BOARD_RGHT-1] = EmptySquare;
8416     } else if (board[fromY][fromX] == king
8417         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8418                && toY == fromY && toX < fromX-1) {
8419         board[fromY][fromX] = EmptySquare;
8420         board[toY][toX] = king;
8421         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8422         board[fromY][BOARD_LEFT] = EmptySquare;
8423     } else if (fromY == 7 && fromX == 3
8424                && board[fromY][fromX] == BlackKing
8425                && toY == 7 && toX == 5) {
8426         board[fromY][fromX] = EmptySquare;
8427         board[toY][toX] = BlackKing;
8428         board[fromY][7] = EmptySquare;
8429         board[toY][4] = BlackRook;
8430     } else if (fromY == 7 && fromX == 3
8431                && board[fromY][fromX] == BlackKing
8432                && toY == 7 && toX == 1) {
8433         board[fromY][fromX] = EmptySquare;
8434         board[toY][toX] = BlackKing;
8435         board[fromY][0] = EmptySquare;
8436         board[toY][2] = BlackRook;
8437     } else if (board[fromY][fromX] == BlackPawn
8438                && toY < promoRank
8439                && gameInfo.variant != VariantXiangqi
8440                ) {
8441         /* black pawn promotion */
8442         board[toY][toX] = CharToPiece(ToLower(promoChar));
8443         if (board[toY][toX] == EmptySquare) {
8444             board[toY][toX] = BlackQueen;
8445         }
8446         if(gameInfo.variant==VariantBughouse ||
8447            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8448             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8449         board[fromY][fromX] = EmptySquare;
8450     } else if ((fromY == 3)
8451                && (toX != fromX)
8452                && gameInfo.variant != VariantXiangqi
8453                && gameInfo.variant != VariantBerolina
8454                && (board[fromY][fromX] == BlackPawn)
8455                && (board[toY][toX] == EmptySquare)) {
8456         board[fromY][fromX] = EmptySquare;
8457         board[toY][toX] = BlackPawn;
8458         captured = board[toY + 1][toX];
8459         board[toY + 1][toX] = EmptySquare;
8460     } else if ((fromY == 3)
8461                && (toX == fromX)
8462                && gameInfo.variant == VariantBerolina
8463                && (board[fromY][fromX] == BlackPawn)
8464                && (board[toY][toX] == EmptySquare)) {
8465         board[fromY][fromX] = EmptySquare;
8466         board[toY][toX] = BlackPawn;
8467         if(oldEP & EP_BEROLIN_A) {
8468                 captured = board[fromY][fromX-1];
8469                 board[fromY][fromX-1] = EmptySquare;
8470         }else{  captured = board[fromY][fromX+1];
8471                 board[fromY][fromX+1] = EmptySquare;
8472         }
8473     } else {
8474         board[toY][toX] = board[fromY][fromX];
8475         board[fromY][fromX] = EmptySquare;
8476     }
8477
8478     /* [HGM] now we promote for Shogi, if needed */
8479     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8480         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8481   }
8482
8483     if (gameInfo.holdingsWidth != 0) {
8484
8485       /* !!A lot more code needs to be written to support holdings  */
8486       /* [HGM] OK, so I have written it. Holdings are stored in the */
8487       /* penultimate board files, so they are automaticlly stored   */
8488       /* in the game history.                                       */
8489       if (fromY == DROP_RANK) {
8490         /* Delete from holdings, by decreasing count */
8491         /* and erasing image if necessary            */
8492         p = (int) fromX;
8493         if(p < (int) BlackPawn) { /* white drop */
8494              p -= (int)WhitePawn;
8495                  p = PieceToNumber((ChessSquare)p);
8496              if(p >= gameInfo.holdingsSize) p = 0;
8497              if(--board[p][BOARD_WIDTH-2] <= 0)
8498                   board[p][BOARD_WIDTH-1] = EmptySquare;
8499              if((int)board[p][BOARD_WIDTH-2] < 0)
8500                         board[p][BOARD_WIDTH-2] = 0;
8501         } else {                  /* black drop */
8502              p -= (int)BlackPawn;
8503                  p = PieceToNumber((ChessSquare)p);
8504              if(p >= gameInfo.holdingsSize) p = 0;
8505              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8506                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8507              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8508                         board[BOARD_HEIGHT-1-p][1] = 0;
8509         }
8510       }
8511       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8512           && gameInfo.variant != VariantBughouse        ) {
8513         /* [HGM] holdings: Add to holdings, if holdings exist */
8514         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8515                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8516                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8517         }
8518         p = (int) captured;
8519         if (p >= (int) BlackPawn) {
8520           p -= (int)BlackPawn;
8521           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8522                   /* in Shogi restore piece to its original  first */
8523                   captured = (ChessSquare) (DEMOTED captured);
8524                   p = DEMOTED p;
8525           }
8526           p = PieceToNumber((ChessSquare)p);
8527           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8528           board[p][BOARD_WIDTH-2]++;
8529           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8530         } else {
8531           p -= (int)WhitePawn;
8532           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8533                   captured = (ChessSquare) (DEMOTED captured);
8534                   p = DEMOTED p;
8535           }
8536           p = PieceToNumber((ChessSquare)p);
8537           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8538           board[BOARD_HEIGHT-1-p][1]++;
8539           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8540         }
8541       }
8542     } else if (gameInfo.variant == VariantAtomic) {
8543       if (captured != EmptySquare) {
8544         int y, x;
8545         for (y = toY-1; y <= toY+1; y++) {
8546           for (x = toX-1; x <= toX+1; x++) {
8547             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8548                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8549               board[y][x] = EmptySquare;
8550             }
8551           }
8552         }
8553         board[toY][toX] = EmptySquare;
8554       }
8555     }
8556     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8557         /* [HGM] Shogi promotions */
8558         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8559     }
8560
8561     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8562                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8563         // [HGM] superchess: take promotion piece out of holdings
8564         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8565         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8566             if(!--board[k][BOARD_WIDTH-2])
8567                 board[k][BOARD_WIDTH-1] = EmptySquare;
8568         } else {
8569             if(!--board[BOARD_HEIGHT-1-k][1])
8570                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8571         }
8572     }
8573
8574 }
8575
8576 /* Updates forwardMostMove */
8577 void
8578 MakeMove(fromX, fromY, toX, toY, promoChar)
8579      int fromX, fromY, toX, toY;
8580      int promoChar;
8581 {
8582 //    forwardMostMove++; // [HGM] bare: moved downstream
8583
8584     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8585         int timeLeft; static int lastLoadFlag=0; int king, piece;
8586         piece = boards[forwardMostMove][fromY][fromX];
8587         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8588         if(gameInfo.variant == VariantKnightmate)
8589             king += (int) WhiteUnicorn - (int) WhiteKing;
8590         if(forwardMostMove == 0) {
8591             if(blackPlaysFirst) 
8592                 fprintf(serverMoves, "%s;", second.tidy);
8593             fprintf(serverMoves, "%s;", first.tidy);
8594             if(!blackPlaysFirst) 
8595                 fprintf(serverMoves, "%s;", second.tidy);
8596         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8597         lastLoadFlag = loadFlag;
8598         // print base move
8599         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8600         // print castling suffix
8601         if( toY == fromY && piece == king ) {
8602             if(toX-fromX > 1)
8603                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8604             if(fromX-toX >1)
8605                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8606         }
8607         // e.p. suffix
8608         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8609              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8610              boards[forwardMostMove][toY][toX] == EmptySquare
8611              && fromX != toX && fromY != toY)
8612                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8613         // promotion suffix
8614         if(promoChar != NULLCHAR)
8615                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8616         if(!loadFlag) {
8617             fprintf(serverMoves, "/%d/%d",
8618                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8619             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8620             else                      timeLeft = blackTimeRemaining/1000;
8621             fprintf(serverMoves, "/%d", timeLeft);
8622         }
8623         fflush(serverMoves);
8624     }
8625
8626     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8627       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8628                         0, 1);
8629       return;
8630     }
8631     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8632     if (commentList[forwardMostMove+1] != NULL) {
8633         free(commentList[forwardMostMove+1]);
8634         commentList[forwardMostMove+1] = NULL;
8635     }
8636     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8637     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8638     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8639     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8640     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8641     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8642     gameInfo.result = GameUnfinished;
8643     if (gameInfo.resultDetails != NULL) {
8644         free(gameInfo.resultDetails);
8645         gameInfo.resultDetails = NULL;
8646     }
8647     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8648                               moveList[forwardMostMove - 1]);
8649     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8650                              PosFlags(forwardMostMove - 1),
8651                              fromY, fromX, toY, toX, promoChar,
8652                              parseList[forwardMostMove - 1]);
8653     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8654       case MT_NONE:
8655       case MT_STALEMATE:
8656       default:
8657         break;
8658       case MT_CHECK:
8659         if(gameInfo.variant != VariantShogi)
8660             strcat(parseList[forwardMostMove - 1], "+");
8661         break;
8662       case MT_CHECKMATE:
8663       case MT_STAINMATE:
8664         strcat(parseList[forwardMostMove - 1], "#");
8665         break;
8666     }
8667     if (appData.debugMode) {
8668         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8669     }
8670
8671 }
8672
8673 /* Updates currentMove if not pausing */
8674 void
8675 ShowMove(fromX, fromY, toX, toY)
8676 {
8677     int instant = (gameMode == PlayFromGameFile) ?
8678         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8679     if(appData.noGUI) return;
8680     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8681         if (!instant) {
8682             if (forwardMostMove == currentMove + 1) {
8683                 AnimateMove(boards[forwardMostMove - 1],
8684                             fromX, fromY, toX, toY);
8685             }
8686             if (appData.highlightLastMove) {
8687                 SetHighlights(fromX, fromY, toX, toY);
8688             }
8689         }
8690         currentMove = forwardMostMove;
8691     }
8692
8693     if (instant) return;
8694
8695     DisplayMove(currentMove - 1);
8696     DrawPosition(FALSE, boards[currentMove]);
8697     DisplayBothClocks();
8698     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8699 }
8700
8701 void SendEgtPath(ChessProgramState *cps)
8702 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8703         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8704
8705         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8706
8707         while(*p) {
8708             char c, *q = name+1, *r, *s;
8709
8710             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8711             while(*p && *p != ',') *q++ = *p++;
8712             *q++ = ':'; *q = 0;
8713             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8714                 strcmp(name, ",nalimov:") == 0 ) {
8715                 // take nalimov path from the menu-changeable option first, if it is defined
8716                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8717                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8718             } else
8719             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8720                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8721                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8722                 s = r = StrStr(s, ":") + 1; // beginning of path info
8723                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8724                 c = *r; *r = 0;             // temporarily null-terminate path info
8725                     *--q = 0;               // strip of trailig ':' from name
8726                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8727                 *r = c;
8728                 SendToProgram(buf,cps);     // send egtbpath command for this format
8729             }
8730             if(*p == ',') p++; // read away comma to position for next format name
8731         }
8732 }
8733
8734 void
8735 InitChessProgram(cps, setup)
8736      ChessProgramState *cps;
8737      int setup; /* [HGM] needed to setup FRC opening position */
8738 {
8739     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8740     if (appData.noChessProgram) return;
8741     hintRequested = FALSE;
8742     bookRequested = FALSE;
8743
8744     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8745     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8746     if(cps->memSize) { /* [HGM] memory */
8747         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8748         SendToProgram(buf, cps);
8749     }
8750     SendEgtPath(cps); /* [HGM] EGT */
8751     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8752         sprintf(buf, "cores %d\n", appData.smpCores);
8753         SendToProgram(buf, cps);
8754     }
8755
8756     SendToProgram(cps->initString, cps);
8757     if (gameInfo.variant != VariantNormal &&
8758         gameInfo.variant != VariantLoadable
8759         /* [HGM] also send variant if board size non-standard */
8760         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8761                                             ) {
8762       char *v = VariantName(gameInfo.variant);
8763       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8764         /* [HGM] in protocol 1 we have to assume all variants valid */
8765         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8766         DisplayFatalError(buf, 0, 1);
8767         return;
8768       }
8769
8770       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8771       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8772       if( gameInfo.variant == VariantXiangqi )
8773            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8774       if( gameInfo.variant == VariantShogi )
8775            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8776       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8777            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8778       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8779                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8780            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8781       if( gameInfo.variant == VariantCourier )
8782            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8783       if( gameInfo.variant == VariantSuper )
8784            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8785       if( gameInfo.variant == VariantGreat )
8786            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8787
8788       if(overruled) {
8789            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8790                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8791            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8792            if(StrStr(cps->variants, b) == NULL) { 
8793                // specific sized variant not known, check if general sizing allowed
8794                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8795                    if(StrStr(cps->variants, "boardsize") == NULL) {
8796                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8797                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8798                        DisplayFatalError(buf, 0, 1);
8799                        return;
8800                    }
8801                    /* [HGM] here we really should compare with the maximum supported board size */
8802                }
8803            }
8804       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8805       sprintf(buf, "variant %s\n", b);
8806       SendToProgram(buf, cps);
8807     }
8808     currentlyInitializedVariant = gameInfo.variant;
8809
8810     /* [HGM] send opening position in FRC to first engine */
8811     if(setup) {
8812           SendToProgram("force\n", cps);
8813           SendBoard(cps, 0);
8814           /* engine is now in force mode! Set flag to wake it up after first move. */
8815           setboardSpoiledMachineBlack = 1;
8816     }
8817
8818     if (cps->sendICS) {
8819       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8820       SendToProgram(buf, cps);
8821     }
8822     cps->maybeThinking = FALSE;
8823     cps->offeredDraw = 0;
8824     if (!appData.icsActive) {
8825         SendTimeControl(cps, movesPerSession, timeControl,
8826                         timeIncrement, appData.searchDepth,
8827                         searchTime);
8828     }
8829     if (appData.showThinking 
8830         // [HGM] thinking: four options require thinking output to be sent
8831         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8832                                 ) {
8833         SendToProgram("post\n", cps);
8834     }
8835     SendToProgram("hard\n", cps);
8836     if (!appData.ponderNextMove) {
8837         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8838            it without being sure what state we are in first.  "hard"
8839            is not a toggle, so that one is OK.
8840          */
8841         SendToProgram("easy\n", cps);
8842     }
8843     if (cps->usePing) {
8844       sprintf(buf, "ping %d\n", ++cps->lastPing);
8845       SendToProgram(buf, cps);
8846     }
8847     cps->initDone = TRUE;
8848 }   
8849
8850
8851 void
8852 StartChessProgram(cps)
8853      ChessProgramState *cps;
8854 {
8855     char buf[MSG_SIZ];
8856     int err;
8857
8858     if (appData.noChessProgram) return;
8859     cps->initDone = FALSE;
8860
8861     if (strcmp(cps->host, "localhost") == 0) {
8862         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8863     } else if (*appData.remoteShell == NULLCHAR) {
8864         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8865     } else {
8866         if (*appData.remoteUser == NULLCHAR) {
8867           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8868                     cps->program);
8869         } else {
8870           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8871                     cps->host, appData.remoteUser, cps->program);
8872         }
8873         err = StartChildProcess(buf, "", &cps->pr);
8874     }
8875     
8876     if (err != 0) {
8877         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8878         DisplayFatalError(buf, err, 1);
8879         cps->pr = NoProc;
8880         cps->isr = NULL;
8881         return;
8882     }
8883     
8884     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8885     if (cps->protocolVersion > 1) {
8886       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8887       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8888       cps->comboCnt = 0;  //                and values of combo boxes
8889       SendToProgram(buf, cps);
8890     } else {
8891       SendToProgram("xboard\n", cps);
8892     }
8893 }
8894
8895
8896 void
8897 TwoMachinesEventIfReady P((void))
8898 {
8899   if (first.lastPing != first.lastPong) {
8900     DisplayMessage("", _("Waiting for first chess program"));
8901     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8902     return;
8903   }
8904   if (second.lastPing != second.lastPong) {
8905     DisplayMessage("", _("Waiting for second chess program"));
8906     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8907     return;
8908   }
8909   ThawUI();
8910   TwoMachinesEvent();
8911 }
8912
8913 void
8914 NextMatchGame P((void))
8915 {
8916     int index; /* [HGM] autoinc: step load index during match */
8917     Reset(FALSE, TRUE);
8918     if (*appData.loadGameFile != NULLCHAR) {
8919         index = appData.loadGameIndex;
8920         if(index < 0) { // [HGM] autoinc
8921             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8922             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8923         } 
8924         LoadGameFromFile(appData.loadGameFile,
8925                          index,
8926                          appData.loadGameFile, FALSE);
8927     } else if (*appData.loadPositionFile != NULLCHAR) {
8928         index = appData.loadPositionIndex;
8929         if(index < 0) { // [HGM] autoinc
8930             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8931             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8932         } 
8933         LoadPositionFromFile(appData.loadPositionFile,
8934                              index,
8935                              appData.loadPositionFile);
8936     }
8937     TwoMachinesEventIfReady();
8938 }
8939
8940 void UserAdjudicationEvent( int result )
8941 {
8942     ChessMove gameResult = GameIsDrawn;
8943
8944     if( result > 0 ) {
8945         gameResult = WhiteWins;
8946     }
8947     else if( result < 0 ) {
8948         gameResult = BlackWins;
8949     }
8950
8951     if( gameMode == TwoMachinesPlay ) {
8952         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8953     }
8954 }
8955
8956
8957 // [HGM] save: calculate checksum of game to make games easily identifiable
8958 int StringCheckSum(char *s)
8959 {
8960         int i = 0;
8961         if(s==NULL) return 0;
8962         while(*s) i = i*259 + *s++;
8963         return i;
8964 }
8965
8966 int GameCheckSum()
8967 {
8968         int i, sum=0;
8969         for(i=backwardMostMove; i<forwardMostMove; i++) {
8970                 sum += pvInfoList[i].depth;
8971                 sum += StringCheckSum(parseList[i]);
8972                 sum += StringCheckSum(commentList[i]);
8973                 sum *= 261;
8974         }
8975         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8976         return sum + StringCheckSum(commentList[i]);
8977 } // end of save patch
8978
8979 void
8980 GameEnds(result, resultDetails, whosays)
8981      ChessMove result;
8982      char *resultDetails;
8983      int whosays;
8984 {
8985     GameMode nextGameMode;
8986     int isIcsGame;
8987     char buf[MSG_SIZ], popupRequested = 0;
8988
8989     if(endingGame) return; /* [HGM] crash: forbid recursion */
8990     endingGame = 1;
8991     if(twoBoards) { // [HGM] dual: switch back to one board
8992         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
8993         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
8994     }
8995     if (appData.debugMode) {
8996       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8997               result, resultDetails ? resultDetails : "(null)", whosays);
8998     }
8999
9000     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9001
9002     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9003         /* If we are playing on ICS, the server decides when the
9004            game is over, but the engine can offer to draw, claim 
9005            a draw, or resign. 
9006          */
9007 #if ZIPPY
9008         if (appData.zippyPlay && first.initDone) {
9009             if (result == GameIsDrawn) {
9010                 /* In case draw still needs to be claimed */
9011                 SendToICS(ics_prefix);
9012                 SendToICS("draw\n");
9013             } else if (StrCaseStr(resultDetails, "resign")) {
9014                 SendToICS(ics_prefix);
9015                 SendToICS("resign\n");
9016             }
9017         }
9018 #endif
9019         endingGame = 0; /* [HGM] crash */
9020         return;
9021     }
9022
9023     /* If we're loading the game from a file, stop */
9024     if (whosays == GE_FILE) {
9025       (void) StopLoadGameTimer();
9026       gameFileFP = NULL;
9027     }
9028
9029     /* Cancel draw offers */
9030     first.offeredDraw = second.offeredDraw = 0;
9031
9032     /* If this is an ICS game, only ICS can really say it's done;
9033        if not, anyone can. */
9034     isIcsGame = (gameMode == IcsPlayingWhite || 
9035                  gameMode == IcsPlayingBlack || 
9036                  gameMode == IcsObserving    || 
9037                  gameMode == IcsExamining);
9038
9039     if (!isIcsGame || whosays == GE_ICS) {
9040         /* OK -- not an ICS game, or ICS said it was done */
9041         StopClocks();
9042         if (!isIcsGame && !appData.noChessProgram) 
9043           SetUserThinkingEnables();
9044     
9045         /* [HGM] if a machine claims the game end we verify this claim */
9046         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9047             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9048                 char claimer;
9049                 ChessMove trueResult = (ChessMove) -1;
9050
9051                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9052                                             first.twoMachinesColor[0] :
9053                                             second.twoMachinesColor[0] ;
9054
9055                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9056                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9057                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9058                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9059                 } else
9060                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9061                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9062                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9063                 } else
9064                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9065                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9066                 }
9067
9068                 // now verify win claims, but not in drop games, as we don't understand those yet
9069                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9070                                                  || gameInfo.variant == VariantGreat) &&
9071                     (result == WhiteWins && claimer == 'w' ||
9072                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9073                       if (appData.debugMode) {
9074                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9075                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9076                       }
9077                       if(result != trueResult) {
9078                               sprintf(buf, "False win claim: '%s'", resultDetails);
9079                               result = claimer == 'w' ? BlackWins : WhiteWins;
9080                               resultDetails = buf;
9081                       }
9082                 } else
9083                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9084                     && (forwardMostMove <= backwardMostMove ||
9085                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9086                         (claimer=='b')==(forwardMostMove&1))
9087                                                                                   ) {
9088                       /* [HGM] verify: draws that were not flagged are false claims */
9089                       sprintf(buf, "False draw claim: '%s'", resultDetails);
9090                       result = claimer == 'w' ? BlackWins : WhiteWins;
9091                       resultDetails = buf;
9092                 }
9093                 /* (Claiming a loss is accepted no questions asked!) */
9094             }
9095             /* [HGM] bare: don't allow bare King to win */
9096             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9097                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
9098                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9099                && result != GameIsDrawn)
9100             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9101                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9102                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9103                         if(p >= 0 && p <= (int)WhiteKing) k++;
9104                 }
9105                 if (appData.debugMode) {
9106                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9107                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9108                 }
9109                 if(k <= 1) {
9110                         result = GameIsDrawn;
9111                         sprintf(buf, "%s but bare king", resultDetails);
9112                         resultDetails = buf;
9113                 }
9114             }
9115         }
9116
9117
9118         if(serverMoves != NULL && !loadFlag) { char c = '=';
9119             if(result==WhiteWins) c = '+';
9120             if(result==BlackWins) c = '-';
9121             if(resultDetails != NULL)
9122                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9123         }
9124         if (resultDetails != NULL) {
9125             gameInfo.result = result;
9126             gameInfo.resultDetails = StrSave(resultDetails);
9127
9128             /* display last move only if game was not loaded from file */
9129             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9130                 DisplayMove(currentMove - 1);
9131     
9132             if (forwardMostMove != 0) {
9133                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9134                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9135                                                                 ) {
9136                     if (*appData.saveGameFile != NULLCHAR) {
9137                         SaveGameToFile(appData.saveGameFile, TRUE);
9138                     } else if (appData.autoSaveGames) {
9139                         AutoSaveGame();
9140                     }
9141                     if (*appData.savePositionFile != NULLCHAR) {
9142                         SavePositionToFile(appData.savePositionFile);
9143                     }
9144                 }
9145             }
9146
9147             /* Tell program how game ended in case it is learning */
9148             /* [HGM] Moved this to after saving the PGN, just in case */
9149             /* engine died and we got here through time loss. In that */
9150             /* case we will get a fatal error writing the pipe, which */
9151             /* would otherwise lose us the PGN.                       */
9152             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9153             /* output during GameEnds should never be fatal anymore   */
9154             if (gameMode == MachinePlaysWhite ||
9155                 gameMode == MachinePlaysBlack ||
9156                 gameMode == TwoMachinesPlay ||
9157                 gameMode == IcsPlayingWhite ||
9158                 gameMode == IcsPlayingBlack ||
9159                 gameMode == BeginningOfGame) {
9160                 char buf[MSG_SIZ];
9161                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9162                         resultDetails);
9163                 if (first.pr != NoProc) {
9164                     SendToProgram(buf, &first);
9165                 }
9166                 if (second.pr != NoProc &&
9167                     gameMode == TwoMachinesPlay) {
9168                     SendToProgram(buf, &second);
9169                 }
9170             }
9171         }
9172
9173         if (appData.icsActive) {
9174             if (appData.quietPlay &&
9175                 (gameMode == IcsPlayingWhite ||
9176                  gameMode == IcsPlayingBlack)) {
9177                 SendToICS(ics_prefix);
9178                 SendToICS("set shout 1\n");
9179             }
9180             nextGameMode = IcsIdle;
9181             ics_user_moved = FALSE;
9182             /* clean up premove.  It's ugly when the game has ended and the
9183              * premove highlights are still on the board.
9184              */
9185             if (gotPremove) {
9186               gotPremove = FALSE;
9187               ClearPremoveHighlights();
9188               DrawPosition(FALSE, boards[currentMove]);
9189             }
9190             if (whosays == GE_ICS) {
9191                 switch (result) {
9192                 case WhiteWins:
9193                     if (gameMode == IcsPlayingWhite)
9194                         PlayIcsWinSound();
9195                     else if(gameMode == IcsPlayingBlack)
9196                         PlayIcsLossSound();
9197                     break;
9198                 case BlackWins:
9199                     if (gameMode == IcsPlayingBlack)
9200                         PlayIcsWinSound();
9201                     else if(gameMode == IcsPlayingWhite)
9202                         PlayIcsLossSound();
9203                     break;
9204                 case GameIsDrawn:
9205                     PlayIcsDrawSound();
9206                     break;
9207                 default:
9208                     PlayIcsUnfinishedSound();
9209                 }
9210             }
9211         } else if (gameMode == EditGame ||
9212                    gameMode == PlayFromGameFile || 
9213                    gameMode == AnalyzeMode || 
9214                    gameMode == AnalyzeFile) {
9215             nextGameMode = gameMode;
9216         } else {
9217             nextGameMode = EndOfGame;
9218         }
9219         pausing = FALSE;
9220         ModeHighlight();
9221     } else {
9222         nextGameMode = gameMode;
9223     }
9224
9225     if (appData.noChessProgram) {
9226         gameMode = nextGameMode;
9227         ModeHighlight();
9228         endingGame = 0; /* [HGM] crash */
9229         return;
9230     }
9231
9232     if (first.reuse) {
9233         /* Put first chess program into idle state */
9234         if (first.pr != NoProc &&
9235             (gameMode == MachinePlaysWhite ||
9236              gameMode == MachinePlaysBlack ||
9237              gameMode == TwoMachinesPlay ||
9238              gameMode == IcsPlayingWhite ||
9239              gameMode == IcsPlayingBlack ||
9240              gameMode == BeginningOfGame)) {
9241             SendToProgram("force\n", &first);
9242             if (first.usePing) {
9243               char buf[MSG_SIZ];
9244               sprintf(buf, "ping %d\n", ++first.lastPing);
9245               SendToProgram(buf, &first);
9246             }
9247         }
9248     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9249         /* Kill off first chess program */
9250         if (first.isr != NULL)
9251           RemoveInputSource(first.isr);
9252         first.isr = NULL;
9253     
9254         if (first.pr != NoProc) {
9255             ExitAnalyzeMode();
9256             DoSleep( appData.delayBeforeQuit );
9257             SendToProgram("quit\n", &first);
9258             DoSleep( appData.delayAfterQuit );
9259             DestroyChildProcess(first.pr, first.useSigterm);
9260         }
9261         first.pr = NoProc;
9262     }
9263     if (second.reuse) {
9264         /* Put second chess program into idle state */
9265         if (second.pr != NoProc &&
9266             gameMode == TwoMachinesPlay) {
9267             SendToProgram("force\n", &second);
9268             if (second.usePing) {
9269               char buf[MSG_SIZ];
9270               sprintf(buf, "ping %d\n", ++second.lastPing);
9271               SendToProgram(buf, &second);
9272             }
9273         }
9274     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9275         /* Kill off second chess program */
9276         if (second.isr != NULL)
9277           RemoveInputSource(second.isr);
9278         second.isr = NULL;
9279     
9280         if (second.pr != NoProc) {
9281             DoSleep( appData.delayBeforeQuit );
9282             SendToProgram("quit\n", &second);
9283             DoSleep( appData.delayAfterQuit );
9284             DestroyChildProcess(second.pr, second.useSigterm);
9285         }
9286         second.pr = NoProc;
9287     }
9288
9289     if (matchMode && gameMode == TwoMachinesPlay) {
9290         switch (result) {
9291         case WhiteWins:
9292           if (first.twoMachinesColor[0] == 'w') {
9293             first.matchWins++;
9294           } else {
9295             second.matchWins++;
9296           }
9297           break;
9298         case BlackWins:
9299           if (first.twoMachinesColor[0] == 'b') {
9300             first.matchWins++;
9301           } else {
9302             second.matchWins++;
9303           }
9304           break;
9305         default:
9306           break;
9307         }
9308         if (matchGame < appData.matchGames) {
9309             char *tmp;
9310             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9311                 tmp = first.twoMachinesColor;
9312                 first.twoMachinesColor = second.twoMachinesColor;
9313                 second.twoMachinesColor = tmp;
9314             }
9315             gameMode = nextGameMode;
9316             matchGame++;
9317             if(appData.matchPause>10000 || appData.matchPause<10)
9318                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9319             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9320             endingGame = 0; /* [HGM] crash */
9321             return;
9322         } else {
9323             char buf[MSG_SIZ];
9324             gameMode = nextGameMode;
9325             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9326                     first.tidy, second.tidy,
9327                     first.matchWins, second.matchWins,
9328                     appData.matchGames - (first.matchWins + second.matchWins));
9329             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9330         }
9331     }
9332     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9333         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9334       ExitAnalyzeMode();
9335     gameMode = nextGameMode;
9336     ModeHighlight();
9337     endingGame = 0;  /* [HGM] crash */
9338     if(popupRequested) DisplayFatalError(buf, 0, 0); // [HGM] crash: this call GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9339 }
9340
9341 /* Assumes program was just initialized (initString sent).
9342    Leaves program in force mode. */
9343 void
9344 FeedMovesToProgram(cps, upto) 
9345      ChessProgramState *cps;
9346      int upto;
9347 {
9348     int i;
9349     
9350     if (appData.debugMode)
9351       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9352               startedFromSetupPosition ? "position and " : "",
9353               backwardMostMove, upto, cps->which);
9354     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9355         // [HGM] variantswitch: make engine aware of new variant
9356         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9357                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9358         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9359         SendToProgram(buf, cps);
9360         currentlyInitializedVariant = gameInfo.variant;
9361     }
9362     SendToProgram("force\n", cps);
9363     if (startedFromSetupPosition) {
9364         SendBoard(cps, backwardMostMove);
9365     if (appData.debugMode) {
9366         fprintf(debugFP, "feedMoves\n");
9367     }
9368     }
9369     for (i = backwardMostMove; i < upto; i++) {
9370         SendMoveToProgram(i, cps);
9371     }
9372 }
9373
9374
9375 void
9376 ResurrectChessProgram()
9377 {
9378      /* The chess program may have exited.
9379         If so, restart it and feed it all the moves made so far. */
9380
9381     if (appData.noChessProgram || first.pr != NoProc) return;
9382     
9383     StartChessProgram(&first);
9384     InitChessProgram(&first, FALSE);
9385     FeedMovesToProgram(&first, currentMove);
9386
9387     if (!first.sendTime) {
9388         /* can't tell gnuchess what its clock should read,
9389            so we bow to its notion. */
9390         ResetClocks();
9391         timeRemaining[0][currentMove] = whiteTimeRemaining;
9392         timeRemaining[1][currentMove] = blackTimeRemaining;
9393     }
9394
9395     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9396                 appData.icsEngineAnalyze) && first.analysisSupport) {
9397       SendToProgram("analyze\n", &first);
9398       first.analyzing = TRUE;
9399     }
9400 }
9401
9402 /*
9403  * Button procedures
9404  */
9405 void
9406 Reset(redraw, init)
9407      int redraw, init;
9408 {
9409     int i;
9410
9411     if (appData.debugMode) {
9412         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9413                 redraw, init, gameMode);
9414     }
9415     CleanupTail(); // [HGM] vari: delete any stored variations
9416     pausing = pauseExamInvalid = FALSE;
9417     startedFromSetupPosition = blackPlaysFirst = FALSE;
9418     firstMove = TRUE;
9419     whiteFlag = blackFlag = FALSE;
9420     userOfferedDraw = FALSE;
9421     hintRequested = bookRequested = FALSE;
9422     first.maybeThinking = FALSE;
9423     second.maybeThinking = FALSE;
9424     first.bookSuspend = FALSE; // [HGM] book
9425     second.bookSuspend = FALSE;
9426     thinkOutput[0] = NULLCHAR;
9427     lastHint[0] = NULLCHAR;
9428     ClearGameInfo(&gameInfo);
9429     gameInfo.variant = StringToVariant(appData.variant);
9430     ics_user_moved = ics_clock_paused = FALSE;
9431     ics_getting_history = H_FALSE;
9432     ics_gamenum = -1;
9433     white_holding[0] = black_holding[0] = NULLCHAR;
9434     ClearProgramStats();
9435     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9436     
9437     ResetFrontEnd();
9438     ClearHighlights();
9439     flipView = appData.flipView;
9440     ClearPremoveHighlights();
9441     gotPremove = FALSE;
9442     alarmSounded = FALSE;
9443
9444     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9445     if(appData.serverMovesName != NULL) {
9446         /* [HGM] prepare to make moves file for broadcasting */
9447         clock_t t = clock();
9448         if(serverMoves != NULL) fclose(serverMoves);
9449         serverMoves = fopen(appData.serverMovesName, "r");
9450         if(serverMoves != NULL) {
9451             fclose(serverMoves);
9452             /* delay 15 sec before overwriting, so all clients can see end */
9453             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9454         }
9455         serverMoves = fopen(appData.serverMovesName, "w");
9456     }
9457
9458     ExitAnalyzeMode();
9459     gameMode = BeginningOfGame;
9460     ModeHighlight();
9461     if(appData.icsActive) gameInfo.variant = VariantNormal;
9462     currentMove = forwardMostMove = backwardMostMove = 0;
9463     InitPosition(redraw);
9464     for (i = 0; i < MAX_MOVES; i++) {
9465         if (commentList[i] != NULL) {
9466             free(commentList[i]);
9467             commentList[i] = NULL;
9468         }
9469     }
9470     ResetClocks();
9471     timeRemaining[0][0] = whiteTimeRemaining;
9472     timeRemaining[1][0] = blackTimeRemaining;
9473     if (first.pr == NULL) {
9474         StartChessProgram(&first);
9475     }
9476     if (init) {
9477             InitChessProgram(&first, startedFromSetupPosition);
9478     }
9479     DisplayTitle("");
9480     DisplayMessage("", "");
9481     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9482     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9483 }
9484
9485 void
9486 AutoPlayGameLoop()
9487 {
9488     for (;;) {
9489         if (!AutoPlayOneMove())
9490           return;
9491         if (matchMode || appData.timeDelay == 0)
9492           continue;
9493         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9494           return;
9495         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9496         break;
9497     }
9498 }
9499
9500
9501 int
9502 AutoPlayOneMove()
9503 {
9504     int fromX, fromY, toX, toY;
9505
9506     if (appData.debugMode) {
9507       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9508     }
9509
9510     if (gameMode != PlayFromGameFile)
9511       return FALSE;
9512
9513     if (currentMove >= forwardMostMove) {
9514       gameMode = EditGame;
9515       ModeHighlight();
9516
9517       /* [AS] Clear current move marker at the end of a game */
9518       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9519
9520       return FALSE;
9521     }
9522     
9523     toX = moveList[currentMove][2] - AAA;
9524     toY = moveList[currentMove][3] - ONE;
9525
9526     if (moveList[currentMove][1] == '@') {
9527         if (appData.highlightLastMove) {
9528             SetHighlights(-1, -1, toX, toY);
9529         }
9530     } else {
9531         fromX = moveList[currentMove][0] - AAA;
9532         fromY = moveList[currentMove][1] - ONE;
9533
9534         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9535
9536         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9537
9538         if (appData.highlightLastMove) {
9539             SetHighlights(fromX, fromY, toX, toY);
9540         }
9541     }
9542     DisplayMove(currentMove);
9543     SendMoveToProgram(currentMove++, &first);
9544     DisplayBothClocks();
9545     DrawPosition(FALSE, boards[currentMove]);
9546     // [HGM] PV info: always display, routine tests if empty
9547     DisplayComment(currentMove - 1, commentList[currentMove]);
9548     return TRUE;
9549 }
9550
9551
9552 int
9553 LoadGameOneMove(readAhead)
9554      ChessMove readAhead;
9555 {
9556     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9557     char promoChar = NULLCHAR;
9558     ChessMove moveType;
9559     char move[MSG_SIZ];
9560     char *p, *q;
9561     
9562     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9563         gameMode != AnalyzeMode && gameMode != Training) {
9564         gameFileFP = NULL;
9565         return FALSE;
9566     }
9567     
9568     yyboardindex = forwardMostMove;
9569     if (readAhead != (ChessMove)0) {
9570       moveType = readAhead;
9571     } else {
9572       if (gameFileFP == NULL)
9573           return FALSE;
9574       moveType = (ChessMove) yylex();
9575     }
9576     
9577     done = FALSE;
9578     switch (moveType) {
9579       case Comment:
9580         if (appData.debugMode) 
9581           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9582         p = yy_text;
9583
9584         /* append the comment but don't display it */
9585         AppendComment(currentMove, p, FALSE);
9586         return TRUE;
9587
9588       case WhiteCapturesEnPassant:
9589       case BlackCapturesEnPassant:
9590       case WhitePromotionChancellor:
9591       case BlackPromotionChancellor:
9592       case WhitePromotionArchbishop:
9593       case BlackPromotionArchbishop:
9594       case WhitePromotionCentaur:
9595       case BlackPromotionCentaur:
9596       case WhitePromotionQueen:
9597       case BlackPromotionQueen:
9598       case WhitePromotionRook:
9599       case BlackPromotionRook:
9600       case WhitePromotionBishop:
9601       case BlackPromotionBishop:
9602       case WhitePromotionKnight:
9603       case BlackPromotionKnight:
9604       case WhitePromotionKing:
9605       case BlackPromotionKing:
9606       case NormalMove:
9607       case WhiteKingSideCastle:
9608       case WhiteQueenSideCastle:
9609       case BlackKingSideCastle:
9610       case BlackQueenSideCastle:
9611       case WhiteKingSideCastleWild:
9612       case WhiteQueenSideCastleWild:
9613       case BlackKingSideCastleWild:
9614       case BlackQueenSideCastleWild:
9615       /* PUSH Fabien */
9616       case WhiteHSideCastleFR:
9617       case WhiteASideCastleFR:
9618       case BlackHSideCastleFR:
9619       case BlackASideCastleFR:
9620       /* POP Fabien */
9621         if (appData.debugMode)
9622           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9623         fromX = currentMoveString[0] - AAA;
9624         fromY = currentMoveString[1] - ONE;
9625         toX = currentMoveString[2] - AAA;
9626         toY = currentMoveString[3] - ONE;
9627         promoChar = currentMoveString[4];
9628         break;
9629
9630       case WhiteDrop:
9631       case BlackDrop:
9632         if (appData.debugMode)
9633           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9634         fromX = moveType == WhiteDrop ?
9635           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9636         (int) CharToPiece(ToLower(currentMoveString[0]));
9637         fromY = DROP_RANK;
9638         toX = currentMoveString[2] - AAA;
9639         toY = currentMoveString[3] - ONE;
9640         break;
9641
9642       case WhiteWins:
9643       case BlackWins:
9644       case GameIsDrawn:
9645       case GameUnfinished:
9646         if (appData.debugMode)
9647           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9648         p = strchr(yy_text, '{');
9649         if (p == NULL) p = strchr(yy_text, '(');
9650         if (p == NULL) {
9651             p = yy_text;
9652             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9653         } else {
9654             q = strchr(p, *p == '{' ? '}' : ')');
9655             if (q != NULL) *q = NULLCHAR;
9656             p++;
9657         }
9658         GameEnds(moveType, p, GE_FILE);
9659         done = TRUE;
9660         if (cmailMsgLoaded) {
9661             ClearHighlights();
9662             flipView = WhiteOnMove(currentMove);
9663             if (moveType == GameUnfinished) flipView = !flipView;
9664             if (appData.debugMode)
9665               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9666         }
9667         break;
9668
9669       case (ChessMove) 0:       /* end of file */
9670         if (appData.debugMode)
9671           fprintf(debugFP, "Parser hit end of file\n");
9672         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9673           case MT_NONE:
9674           case MT_CHECK:
9675             break;
9676           case MT_CHECKMATE:
9677           case MT_STAINMATE:
9678             if (WhiteOnMove(currentMove)) {
9679                 GameEnds(BlackWins, "Black mates", GE_FILE);
9680             } else {
9681                 GameEnds(WhiteWins, "White mates", GE_FILE);
9682             }
9683             break;
9684           case MT_STALEMATE:
9685             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9686             break;
9687         }
9688         done = TRUE;
9689         break;
9690
9691       case MoveNumberOne:
9692         if (lastLoadGameStart == GNUChessGame) {
9693             /* GNUChessGames have numbers, but they aren't move numbers */
9694             if (appData.debugMode)
9695               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9696                       yy_text, (int) moveType);
9697             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9698         }
9699         /* else fall thru */
9700
9701       case XBoardGame:
9702       case GNUChessGame:
9703       case PGNTag:
9704         /* Reached start of next game in file */
9705         if (appData.debugMode)
9706           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9707         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9708           case MT_NONE:
9709           case MT_CHECK:
9710             break;
9711           case MT_CHECKMATE:
9712           case MT_STAINMATE:
9713             if (WhiteOnMove(currentMove)) {
9714                 GameEnds(BlackWins, "Black mates", GE_FILE);
9715             } else {
9716                 GameEnds(WhiteWins, "White mates", GE_FILE);
9717             }
9718             break;
9719           case MT_STALEMATE:
9720             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9721             break;
9722         }
9723         done = TRUE;
9724         break;
9725
9726       case PositionDiagram:     /* should not happen; ignore */
9727       case ElapsedTime:         /* ignore */
9728       case NAG:                 /* ignore */
9729         if (appData.debugMode)
9730           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9731                   yy_text, (int) moveType);
9732         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9733
9734       case IllegalMove:
9735         if (appData.testLegality) {
9736             if (appData.debugMode)
9737               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9738             sprintf(move, _("Illegal move: %d.%s%s"),
9739                     (forwardMostMove / 2) + 1,
9740                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9741             DisplayError(move, 0);
9742             done = TRUE;
9743         } else {
9744             if (appData.debugMode)
9745               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9746                       yy_text, currentMoveString);
9747             fromX = currentMoveString[0] - AAA;
9748             fromY = currentMoveString[1] - ONE;
9749             toX = currentMoveString[2] - AAA;
9750             toY = currentMoveString[3] - ONE;
9751             promoChar = currentMoveString[4];
9752         }
9753         break;
9754
9755       case AmbiguousMove:
9756         if (appData.debugMode)
9757           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9758         sprintf(move, _("Ambiguous move: %d.%s%s"),
9759                 (forwardMostMove / 2) + 1,
9760                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9761         DisplayError(move, 0);
9762         done = TRUE;
9763         break;
9764
9765       default:
9766       case ImpossibleMove:
9767         if (appData.debugMode)
9768           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9769         sprintf(move, _("Illegal move: %d.%s%s"),
9770                 (forwardMostMove / 2) + 1,
9771                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9772         DisplayError(move, 0);
9773         done = TRUE;
9774         break;
9775     }
9776
9777     if (done) {
9778         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9779             DrawPosition(FALSE, boards[currentMove]);
9780             DisplayBothClocks();
9781             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9782               DisplayComment(currentMove - 1, commentList[currentMove]);
9783         }
9784         (void) StopLoadGameTimer();
9785         gameFileFP = NULL;
9786         cmailOldMove = forwardMostMove;
9787         return FALSE;
9788     } else {
9789         /* currentMoveString is set as a side-effect of yylex */
9790         strcat(currentMoveString, "\n");
9791         strcpy(moveList[forwardMostMove], currentMoveString);
9792         
9793         thinkOutput[0] = NULLCHAR;
9794         MakeMove(fromX, fromY, toX, toY, promoChar);
9795         currentMove = forwardMostMove;
9796         return TRUE;
9797     }
9798 }
9799
9800 /* Load the nth game from the given file */
9801 int
9802 LoadGameFromFile(filename, n, title, useList)
9803      char *filename;
9804      int n;
9805      char *title;
9806      /*Boolean*/ int useList;
9807 {
9808     FILE *f;
9809     char buf[MSG_SIZ];
9810
9811     if (strcmp(filename, "-") == 0) {
9812         f = stdin;
9813         title = "stdin";
9814     } else {
9815         f = fopen(filename, "rb");
9816         if (f == NULL) {
9817           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9818             DisplayError(buf, errno);
9819             return FALSE;
9820         }
9821     }
9822     if (fseek(f, 0, 0) == -1) {
9823         /* f is not seekable; probably a pipe */
9824         useList = FALSE;
9825     }
9826     if (useList && n == 0) {
9827         int error = GameListBuild(f);
9828         if (error) {
9829             DisplayError(_("Cannot build game list"), error);
9830         } else if (!ListEmpty(&gameList) &&
9831                    ((ListGame *) gameList.tailPred)->number > 1) {
9832             GameListPopUp(f, title);
9833             return TRUE;
9834         }
9835         GameListDestroy();
9836         n = 1;
9837     }
9838     if (n == 0) n = 1;
9839     return LoadGame(f, n, title, FALSE);
9840 }
9841
9842
9843 void
9844 MakeRegisteredMove()
9845 {
9846     int fromX, fromY, toX, toY;
9847     char promoChar;
9848     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9849         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9850           case CMAIL_MOVE:
9851           case CMAIL_DRAW:
9852             if (appData.debugMode)
9853               fprintf(debugFP, "Restoring %s for game %d\n",
9854                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9855     
9856             thinkOutput[0] = NULLCHAR;
9857             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9858             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9859             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9860             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9861             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9862             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9863             MakeMove(fromX, fromY, toX, toY, promoChar);
9864             ShowMove(fromX, fromY, toX, toY);
9865               
9866             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9867               case MT_NONE:
9868               case MT_CHECK:
9869                 break;
9870                 
9871               case MT_CHECKMATE:
9872               case MT_STAINMATE:
9873                 if (WhiteOnMove(currentMove)) {
9874                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9875                 } else {
9876                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9877                 }
9878                 break;
9879                 
9880               case MT_STALEMATE:
9881                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9882                 break;
9883             }
9884
9885             break;
9886             
9887           case CMAIL_RESIGN:
9888             if (WhiteOnMove(currentMove)) {
9889                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9890             } else {
9891                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9892             }
9893             break;
9894             
9895           case CMAIL_ACCEPT:
9896             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9897             break;
9898               
9899           default:
9900             break;
9901         }
9902     }
9903
9904     return;
9905 }
9906
9907 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9908 int
9909 CmailLoadGame(f, gameNumber, title, useList)
9910      FILE *f;
9911      int gameNumber;
9912      char *title;
9913      int useList;
9914 {
9915     int retVal;
9916
9917     if (gameNumber > nCmailGames) {
9918         DisplayError(_("No more games in this message"), 0);
9919         return FALSE;
9920     }
9921     if (f == lastLoadGameFP) {
9922         int offset = gameNumber - lastLoadGameNumber;
9923         if (offset == 0) {
9924             cmailMsg[0] = NULLCHAR;
9925             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9926                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9927                 nCmailMovesRegistered--;
9928             }
9929             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9930             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9931                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9932             }
9933         } else {
9934             if (! RegisterMove()) return FALSE;
9935         }
9936     }
9937
9938     retVal = LoadGame(f, gameNumber, title, useList);
9939
9940     /* Make move registered during previous look at this game, if any */
9941     MakeRegisteredMove();
9942
9943     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9944         commentList[currentMove]
9945           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9946         DisplayComment(currentMove - 1, commentList[currentMove]);
9947     }
9948
9949     return retVal;
9950 }
9951
9952 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9953 int
9954 ReloadGame(offset)
9955      int offset;
9956 {
9957     int gameNumber = lastLoadGameNumber + offset;
9958     if (lastLoadGameFP == NULL) {
9959         DisplayError(_("No game has been loaded yet"), 0);
9960         return FALSE;
9961     }
9962     if (gameNumber <= 0) {
9963         DisplayError(_("Can't back up any further"), 0);
9964         return FALSE;
9965     }
9966     if (cmailMsgLoaded) {
9967         return CmailLoadGame(lastLoadGameFP, gameNumber,
9968                              lastLoadGameTitle, lastLoadGameUseList);
9969     } else {
9970         return LoadGame(lastLoadGameFP, gameNumber,
9971                         lastLoadGameTitle, lastLoadGameUseList);
9972     }
9973 }
9974
9975
9976
9977 /* Load the nth game from open file f */
9978 int
9979 LoadGame(f, gameNumber, title, useList)
9980      FILE *f;
9981      int gameNumber;
9982      char *title;
9983      int useList;
9984 {
9985     ChessMove cm;
9986     char buf[MSG_SIZ];
9987     int gn = gameNumber;
9988     ListGame *lg = NULL;
9989     int numPGNTags = 0;
9990     int err;
9991     GameMode oldGameMode;
9992     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9993
9994     if (appData.debugMode) 
9995         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9996
9997     if (gameMode == Training )
9998         SetTrainingModeOff();
9999
10000     oldGameMode = gameMode;
10001     if (gameMode != BeginningOfGame) {
10002       Reset(FALSE, TRUE);
10003     }
10004
10005     gameFileFP = f;
10006     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10007         fclose(lastLoadGameFP);
10008     }
10009
10010     if (useList) {
10011         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10012         
10013         if (lg) {
10014             fseek(f, lg->offset, 0);
10015             GameListHighlight(gameNumber);
10016             gn = 1;
10017         }
10018         else {
10019             DisplayError(_("Game number out of range"), 0);
10020             return FALSE;
10021         }
10022     } else {
10023         GameListDestroy();
10024         if (fseek(f, 0, 0) == -1) {
10025             if (f == lastLoadGameFP ?
10026                 gameNumber == lastLoadGameNumber + 1 :
10027                 gameNumber == 1) {
10028                 gn = 1;
10029             } else {
10030                 DisplayError(_("Can't seek on game file"), 0);
10031                 return FALSE;
10032             }
10033         }
10034     }
10035     lastLoadGameFP = f;
10036     lastLoadGameNumber = gameNumber;
10037     strcpy(lastLoadGameTitle, title);
10038     lastLoadGameUseList = useList;
10039
10040     yynewfile(f);
10041
10042     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10043       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10044                 lg->gameInfo.black);
10045             DisplayTitle(buf);
10046     } else if (*title != NULLCHAR) {
10047         if (gameNumber > 1) {
10048             sprintf(buf, "%s %d", title, gameNumber);
10049             DisplayTitle(buf);
10050         } else {
10051             DisplayTitle(title);
10052         }
10053     }
10054
10055     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10056         gameMode = PlayFromGameFile;
10057         ModeHighlight();
10058     }
10059
10060     currentMove = forwardMostMove = backwardMostMove = 0;
10061     CopyBoard(boards[0], initialPosition);
10062     StopClocks();
10063
10064     /*
10065      * Skip the first gn-1 games in the file.
10066      * Also skip over anything that precedes an identifiable 
10067      * start of game marker, to avoid being confused by 
10068      * garbage at the start of the file.  Currently 
10069      * recognized start of game markers are the move number "1",
10070      * the pattern "gnuchess .* game", the pattern
10071      * "^[#;%] [^ ]* game file", and a PGN tag block.  
10072      * A game that starts with one of the latter two patterns
10073      * will also have a move number 1, possibly
10074      * following a position diagram.
10075      * 5-4-02: Let's try being more lenient and allowing a game to
10076      * start with an unnumbered move.  Does that break anything?
10077      */
10078     cm = lastLoadGameStart = (ChessMove) 0;
10079     while (gn > 0) {
10080         yyboardindex = forwardMostMove;
10081         cm = (ChessMove) yylex();
10082         switch (cm) {
10083           case (ChessMove) 0:
10084             if (cmailMsgLoaded) {
10085                 nCmailGames = CMAIL_MAX_GAMES - gn;
10086             } else {
10087                 Reset(TRUE, TRUE);
10088                 DisplayError(_("Game not found in file"), 0);
10089             }
10090             return FALSE;
10091
10092           case GNUChessGame:
10093           case XBoardGame:
10094             gn--;
10095             lastLoadGameStart = cm;
10096             break;
10097             
10098           case MoveNumberOne:
10099             switch (lastLoadGameStart) {
10100               case GNUChessGame:
10101               case XBoardGame:
10102               case PGNTag:
10103                 break;
10104               case MoveNumberOne:
10105               case (ChessMove) 0:
10106                 gn--;           /* count this game */
10107                 lastLoadGameStart = cm;
10108                 break;
10109               default:
10110                 /* impossible */
10111                 break;
10112             }
10113             break;
10114
10115           case PGNTag:
10116             switch (lastLoadGameStart) {
10117               case GNUChessGame:
10118               case PGNTag:
10119               case MoveNumberOne:
10120               case (ChessMove) 0:
10121                 gn--;           /* count this game */
10122                 lastLoadGameStart = cm;
10123                 break;
10124               case XBoardGame:
10125                 lastLoadGameStart = cm; /* game counted already */
10126                 break;
10127               default:
10128                 /* impossible */
10129                 break;
10130             }
10131             if (gn > 0) {
10132                 do {
10133                     yyboardindex = forwardMostMove;
10134                     cm = (ChessMove) yylex();
10135                 } while (cm == PGNTag || cm == Comment);
10136             }
10137             break;
10138
10139           case WhiteWins:
10140           case BlackWins:
10141           case GameIsDrawn:
10142             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10143                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10144                     != CMAIL_OLD_RESULT) {
10145                     nCmailResults ++ ;
10146                     cmailResult[  CMAIL_MAX_GAMES
10147                                 - gn - 1] = CMAIL_OLD_RESULT;
10148                 }
10149             }
10150             break;
10151
10152           case NormalMove:
10153             /* Only a NormalMove can be at the start of a game
10154              * without a position diagram. */
10155             if (lastLoadGameStart == (ChessMove) 0) {
10156               gn--;
10157               lastLoadGameStart = MoveNumberOne;
10158             }
10159             break;
10160
10161           default:
10162             break;
10163         }
10164     }
10165     
10166     if (appData.debugMode)
10167       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10168
10169     if (cm == XBoardGame) {
10170         /* Skip any header junk before position diagram and/or move 1 */
10171         for (;;) {
10172             yyboardindex = forwardMostMove;
10173             cm = (ChessMove) yylex();
10174
10175             if (cm == (ChessMove) 0 ||
10176                 cm == GNUChessGame || cm == XBoardGame) {
10177                 /* Empty game; pretend end-of-file and handle later */
10178                 cm = (ChessMove) 0;
10179                 break;
10180             }
10181
10182             if (cm == MoveNumberOne || cm == PositionDiagram ||
10183                 cm == PGNTag || cm == Comment)
10184               break;
10185         }
10186     } else if (cm == GNUChessGame) {
10187         if (gameInfo.event != NULL) {
10188             free(gameInfo.event);
10189         }
10190         gameInfo.event = StrSave(yy_text);
10191     }   
10192
10193     startedFromSetupPosition = FALSE;
10194     while (cm == PGNTag) {
10195         if (appData.debugMode) 
10196           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10197         err = ParsePGNTag(yy_text, &gameInfo);
10198         if (!err) numPGNTags++;
10199
10200         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10201         if(gameInfo.variant != oldVariant) {
10202             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10203             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10204             InitPosition(TRUE);
10205             oldVariant = gameInfo.variant;
10206             if (appData.debugMode) 
10207               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10208         }
10209
10210
10211         if (gameInfo.fen != NULL) {
10212           Board initial_position;
10213           startedFromSetupPosition = TRUE;
10214           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10215             Reset(TRUE, TRUE);
10216             DisplayError(_("Bad FEN position in file"), 0);
10217             return FALSE;
10218           }
10219           CopyBoard(boards[0], initial_position);
10220           if (blackPlaysFirst) {
10221             currentMove = forwardMostMove = backwardMostMove = 1;
10222             CopyBoard(boards[1], initial_position);
10223             strcpy(moveList[0], "");
10224             strcpy(parseList[0], "");
10225             timeRemaining[0][1] = whiteTimeRemaining;
10226             timeRemaining[1][1] = blackTimeRemaining;
10227             if (commentList[0] != NULL) {
10228               commentList[1] = commentList[0];
10229               commentList[0] = NULL;
10230             }
10231           } else {
10232             currentMove = forwardMostMove = backwardMostMove = 0;
10233           }
10234           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10235           {   int i;
10236               initialRulePlies = FENrulePlies;
10237               for( i=0; i< nrCastlingRights; i++ )
10238                   initialRights[i] = initial_position[CASTLING][i];
10239           }
10240           yyboardindex = forwardMostMove;
10241           free(gameInfo.fen);
10242           gameInfo.fen = NULL;
10243         }
10244
10245         yyboardindex = forwardMostMove;
10246         cm = (ChessMove) yylex();
10247
10248         /* Handle comments interspersed among the tags */
10249         while (cm == Comment) {
10250             char *p;
10251             if (appData.debugMode) 
10252               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10253             p = yy_text;
10254             AppendComment(currentMove, p, FALSE);
10255             yyboardindex = forwardMostMove;
10256             cm = (ChessMove) yylex();
10257         }
10258     }
10259
10260     /* don't rely on existence of Event tag since if game was
10261      * pasted from clipboard the Event tag may not exist
10262      */
10263     if (numPGNTags > 0){
10264         char *tags;
10265         if (gameInfo.variant == VariantNormal) {
10266           VariantClass v = StringToVariant(gameInfo.event);
10267           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10268           if(v < VariantShogi) gameInfo.variant = v;
10269         }
10270         if (!matchMode) {
10271           if( appData.autoDisplayTags ) {
10272             tags = PGNTags(&gameInfo);
10273             TagsPopUp(tags, CmailMsg());
10274             free(tags);
10275           }
10276         }
10277     } else {
10278         /* Make something up, but don't display it now */
10279         SetGameInfo();
10280         TagsPopDown();
10281     }
10282
10283     if (cm == PositionDiagram) {
10284         int i, j;
10285         char *p;
10286         Board initial_position;
10287
10288         if (appData.debugMode)
10289           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10290
10291         if (!startedFromSetupPosition) {
10292             p = yy_text;
10293             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10294               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10295                 switch (*p) {
10296                   case '[':
10297                   case '-':
10298                   case ' ':
10299                   case '\t':
10300                   case '\n':
10301                   case '\r':
10302                     break;
10303                   default:
10304                     initial_position[i][j++] = CharToPiece(*p);
10305                     break;
10306                 }
10307             while (*p == ' ' || *p == '\t' ||
10308                    *p == '\n' || *p == '\r') p++;
10309         
10310             if (strncmp(p, "black", strlen("black"))==0)
10311               blackPlaysFirst = TRUE;
10312             else
10313               blackPlaysFirst = FALSE;
10314             startedFromSetupPosition = TRUE;
10315         
10316             CopyBoard(boards[0], initial_position);
10317             if (blackPlaysFirst) {
10318                 currentMove = forwardMostMove = backwardMostMove = 1;
10319                 CopyBoard(boards[1], initial_position);
10320                 strcpy(moveList[0], "");
10321                 strcpy(parseList[0], "");
10322                 timeRemaining[0][1] = whiteTimeRemaining;
10323                 timeRemaining[1][1] = blackTimeRemaining;
10324                 if (commentList[0] != NULL) {
10325                     commentList[1] = commentList[0];
10326                     commentList[0] = NULL;
10327                 }
10328             } else {
10329                 currentMove = forwardMostMove = backwardMostMove = 0;
10330             }
10331         }
10332         yyboardindex = forwardMostMove;
10333         cm = (ChessMove) yylex();
10334     }
10335
10336     if (first.pr == NoProc) {
10337         StartChessProgram(&first);
10338     }
10339     InitChessProgram(&first, FALSE);
10340     SendToProgram("force\n", &first);
10341     if (startedFromSetupPosition) {
10342         SendBoard(&first, forwardMostMove);
10343     if (appData.debugMode) {
10344         fprintf(debugFP, "Load Game\n");
10345     }
10346         DisplayBothClocks();
10347     }      
10348
10349     /* [HGM] server: flag to write setup moves in broadcast file as one */
10350     loadFlag = appData.suppressLoadMoves;
10351
10352     while (cm == Comment) {
10353         char *p;
10354         if (appData.debugMode) 
10355           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10356         p = yy_text;
10357         AppendComment(currentMove, p, FALSE);
10358         yyboardindex = forwardMostMove;
10359         cm = (ChessMove) yylex();
10360     }
10361
10362     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10363         cm == WhiteWins || cm == BlackWins ||
10364         cm == GameIsDrawn || cm == GameUnfinished) {
10365         DisplayMessage("", _("No moves in game"));
10366         if (cmailMsgLoaded) {
10367             if (appData.debugMode)
10368               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10369             ClearHighlights();
10370             flipView = FALSE;
10371         }
10372         DrawPosition(FALSE, boards[currentMove]);
10373         DisplayBothClocks();
10374         gameMode = EditGame;
10375         ModeHighlight();
10376         gameFileFP = NULL;
10377         cmailOldMove = 0;
10378         return TRUE;
10379     }
10380
10381     // [HGM] PV info: routine tests if comment empty
10382     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10383         DisplayComment(currentMove - 1, commentList[currentMove]);
10384     }
10385     if (!matchMode && appData.timeDelay != 0) 
10386       DrawPosition(FALSE, boards[currentMove]);
10387
10388     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10389       programStats.ok_to_send = 1;
10390     }
10391
10392     /* if the first token after the PGN tags is a move
10393      * and not move number 1, retrieve it from the parser 
10394      */
10395     if (cm != MoveNumberOne)
10396         LoadGameOneMove(cm);
10397
10398     /* load the remaining moves from the file */
10399     while (LoadGameOneMove((ChessMove)0)) {
10400       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10401       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10402     }
10403
10404     /* rewind to the start of the game */
10405     currentMove = backwardMostMove;
10406
10407     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10408
10409     if (oldGameMode == AnalyzeFile ||
10410         oldGameMode == AnalyzeMode) {
10411       AnalyzeFileEvent();
10412     }
10413
10414     if (matchMode || appData.timeDelay == 0) {
10415       ToEndEvent();
10416       gameMode = EditGame;
10417       ModeHighlight();
10418     } else if (appData.timeDelay > 0) {
10419       AutoPlayGameLoop();
10420     }
10421
10422     if (appData.debugMode) 
10423         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10424
10425     loadFlag = 0; /* [HGM] true game starts */
10426     return TRUE;
10427 }
10428
10429 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10430 int
10431 ReloadPosition(offset)
10432      int offset;
10433 {
10434     int positionNumber = lastLoadPositionNumber + offset;
10435     if (lastLoadPositionFP == NULL) {
10436         DisplayError(_("No position has been loaded yet"), 0);
10437         return FALSE;
10438     }
10439     if (positionNumber <= 0) {
10440         DisplayError(_("Can't back up any further"), 0);
10441         return FALSE;
10442     }
10443     return LoadPosition(lastLoadPositionFP, positionNumber,
10444                         lastLoadPositionTitle);
10445 }
10446
10447 /* Load the nth position from the given file */
10448 int
10449 LoadPositionFromFile(filename, n, title)
10450      char *filename;
10451      int n;
10452      char *title;
10453 {
10454     FILE *f;
10455     char buf[MSG_SIZ];
10456
10457     if (strcmp(filename, "-") == 0) {
10458         return LoadPosition(stdin, n, "stdin");
10459     } else {
10460         f = fopen(filename, "rb");
10461         if (f == NULL) {
10462             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10463             DisplayError(buf, errno);
10464             return FALSE;
10465         } else {
10466             return LoadPosition(f, n, title);
10467         }
10468     }
10469 }
10470
10471 /* Load the nth position from the given open file, and close it */
10472 int
10473 LoadPosition(f, positionNumber, title)
10474      FILE *f;
10475      int positionNumber;
10476      char *title;
10477 {
10478     char *p, line[MSG_SIZ];
10479     Board initial_position;
10480     int i, j, fenMode, pn;
10481     
10482     if (gameMode == Training )
10483         SetTrainingModeOff();
10484
10485     if (gameMode != BeginningOfGame) {
10486         Reset(FALSE, TRUE);
10487     }
10488     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10489         fclose(lastLoadPositionFP);
10490     }
10491     if (positionNumber == 0) positionNumber = 1;
10492     lastLoadPositionFP = f;
10493     lastLoadPositionNumber = positionNumber;
10494     strcpy(lastLoadPositionTitle, title);
10495     if (first.pr == NoProc) {
10496       StartChessProgram(&first);
10497       InitChessProgram(&first, FALSE);
10498     }    
10499     pn = positionNumber;
10500     if (positionNumber < 0) {
10501         /* Negative position number means to seek to that byte offset */
10502         if (fseek(f, -positionNumber, 0) == -1) {
10503             DisplayError(_("Can't seek on position file"), 0);
10504             return FALSE;
10505         };
10506         pn = 1;
10507     } else {
10508         if (fseek(f, 0, 0) == -1) {
10509             if (f == lastLoadPositionFP ?
10510                 positionNumber == lastLoadPositionNumber + 1 :
10511                 positionNumber == 1) {
10512                 pn = 1;
10513             } else {
10514                 DisplayError(_("Can't seek on position file"), 0);
10515                 return FALSE;
10516             }
10517         }
10518     }
10519     /* See if this file is FEN or old-style xboard */
10520     if (fgets(line, MSG_SIZ, f) == NULL) {
10521         DisplayError(_("Position not found in file"), 0);
10522         return FALSE;
10523     }
10524     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10525     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10526
10527     if (pn >= 2) {
10528         if (fenMode || line[0] == '#') pn--;
10529         while (pn > 0) {
10530             /* skip positions before number pn */
10531             if (fgets(line, MSG_SIZ, f) == NULL) {
10532                 Reset(TRUE, TRUE);
10533                 DisplayError(_("Position not found in file"), 0);
10534                 return FALSE;
10535             }
10536             if (fenMode || line[0] == '#') pn--;
10537         }
10538     }
10539
10540     if (fenMode) {
10541         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10542             DisplayError(_("Bad FEN position in file"), 0);
10543             return FALSE;
10544         }
10545     } else {
10546         (void) fgets(line, MSG_SIZ, f);
10547         (void) fgets(line, MSG_SIZ, f);
10548     
10549         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10550             (void) fgets(line, MSG_SIZ, f);
10551             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10552                 if (*p == ' ')
10553                   continue;
10554                 initial_position[i][j++] = CharToPiece(*p);
10555             }
10556         }
10557     
10558         blackPlaysFirst = FALSE;
10559         if (!feof(f)) {
10560             (void) fgets(line, MSG_SIZ, f);
10561             if (strncmp(line, "black", strlen("black"))==0)
10562               blackPlaysFirst = TRUE;
10563         }
10564     }
10565     startedFromSetupPosition = TRUE;
10566     
10567     SendToProgram("force\n", &first);
10568     CopyBoard(boards[0], initial_position);
10569     if (blackPlaysFirst) {
10570         currentMove = forwardMostMove = backwardMostMove = 1;
10571         strcpy(moveList[0], "");
10572         strcpy(parseList[0], "");
10573         CopyBoard(boards[1], initial_position);
10574         DisplayMessage("", _("Black to play"));
10575     } else {
10576         currentMove = forwardMostMove = backwardMostMove = 0;
10577         DisplayMessage("", _("White to play"));
10578     }
10579     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10580     SendBoard(&first, forwardMostMove);
10581     if (appData.debugMode) {
10582 int i, j;
10583   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10584   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10585         fprintf(debugFP, "Load Position\n");
10586     }
10587
10588     if (positionNumber > 1) {
10589         sprintf(line, "%s %d", title, positionNumber);
10590         DisplayTitle(line);
10591     } else {
10592         DisplayTitle(title);
10593     }
10594     gameMode = EditGame;
10595     ModeHighlight();
10596     ResetClocks();
10597     timeRemaining[0][1] = whiteTimeRemaining;
10598     timeRemaining[1][1] = blackTimeRemaining;
10599     DrawPosition(FALSE, boards[currentMove]);
10600    
10601     return TRUE;
10602 }
10603
10604
10605 void
10606 CopyPlayerNameIntoFileName(dest, src)
10607      char **dest, *src;
10608 {
10609     while (*src != NULLCHAR && *src != ',') {
10610         if (*src == ' ') {
10611             *(*dest)++ = '_';
10612             src++;
10613         } else {
10614             *(*dest)++ = *src++;
10615         }
10616     }
10617 }
10618
10619 char *DefaultFileName(ext)
10620      char *ext;
10621 {
10622     static char def[MSG_SIZ];
10623     char *p;
10624
10625     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10626         p = def;
10627         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10628         *p++ = '-';
10629         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10630         *p++ = '.';
10631         strcpy(p, ext);
10632     } else {
10633         def[0] = NULLCHAR;
10634     }
10635     return def;
10636 }
10637
10638 /* Save the current game to the given file */
10639 int
10640 SaveGameToFile(filename, append)
10641      char *filename;
10642      int append;
10643 {
10644     FILE *f;
10645     char buf[MSG_SIZ];
10646
10647     if (strcmp(filename, "-") == 0) {
10648         return SaveGame(stdout, 0, NULL);
10649     } else {
10650         f = fopen(filename, append ? "a" : "w");
10651         if (f == NULL) {
10652             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10653             DisplayError(buf, errno);
10654             return FALSE;
10655         } else {
10656             return SaveGame(f, 0, NULL);
10657         }
10658     }
10659 }
10660
10661 char *
10662 SavePart(str)
10663      char *str;
10664 {
10665     static char buf[MSG_SIZ];
10666     char *p;
10667     
10668     p = strchr(str, ' ');
10669     if (p == NULL) return str;
10670     strncpy(buf, str, p - str);
10671     buf[p - str] = NULLCHAR;
10672     return buf;
10673 }
10674
10675 #define PGN_MAX_LINE 75
10676
10677 #define PGN_SIDE_WHITE  0
10678 #define PGN_SIDE_BLACK  1
10679
10680 /* [AS] */
10681 static int FindFirstMoveOutOfBook( int side )
10682 {
10683     int result = -1;
10684
10685     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10686         int index = backwardMostMove;
10687         int has_book_hit = 0;
10688
10689         if( (index % 2) != side ) {
10690             index++;
10691         }
10692
10693         while( index < forwardMostMove ) {
10694             /* Check to see if engine is in book */
10695             int depth = pvInfoList[index].depth;
10696             int score = pvInfoList[index].score;
10697             int in_book = 0;
10698
10699             if( depth <= 2 ) {
10700                 in_book = 1;
10701             }
10702             else if( score == 0 && depth == 63 ) {
10703                 in_book = 1; /* Zappa */
10704             }
10705             else if( score == 2 && depth == 99 ) {
10706                 in_book = 1; /* Abrok */
10707             }
10708
10709             has_book_hit += in_book;
10710
10711             if( ! in_book ) {
10712                 result = index;
10713
10714                 break;
10715             }
10716
10717             index += 2;
10718         }
10719     }
10720
10721     return result;
10722 }
10723
10724 /* [AS] */
10725 void GetOutOfBookInfo( char * buf )
10726 {
10727     int oob[2];
10728     int i;
10729     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10730
10731     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10732     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10733
10734     *buf = '\0';
10735
10736     if( oob[0] >= 0 || oob[1] >= 0 ) {
10737         for( i=0; i<2; i++ ) {
10738             int idx = oob[i];
10739
10740             if( idx >= 0 ) {
10741                 if( i > 0 && oob[0] >= 0 ) {
10742                     strcat( buf, "   " );
10743                 }
10744
10745                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10746                 sprintf( buf+strlen(buf), "%s%.2f", 
10747                     pvInfoList[idx].score >= 0 ? "+" : "",
10748                     pvInfoList[idx].score / 100.0 );
10749             }
10750         }
10751     }
10752 }
10753
10754 /* Save game in PGN style and close the file */
10755 int
10756 SaveGamePGN(f)
10757      FILE *f;
10758 {
10759     int i, offset, linelen, newblock;
10760     time_t tm;
10761 //    char *movetext;
10762     char numtext[32];
10763     int movelen, numlen, blank;
10764     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10765
10766     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10767     
10768     tm = time((time_t *) NULL);
10769     
10770     PrintPGNTags(f, &gameInfo);
10771     
10772     if (backwardMostMove > 0 || startedFromSetupPosition) {
10773         char *fen = PositionToFEN(backwardMostMove, NULL);
10774         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10775         fprintf(f, "\n{--------------\n");
10776         PrintPosition(f, backwardMostMove);
10777         fprintf(f, "--------------}\n");
10778         free(fen);
10779     }
10780     else {
10781         /* [AS] Out of book annotation */
10782         if( appData.saveOutOfBookInfo ) {
10783             char buf[64];
10784
10785             GetOutOfBookInfo( buf );
10786
10787             if( buf[0] != '\0' ) {
10788                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10789             }
10790         }
10791
10792         fprintf(f, "\n");
10793     }
10794
10795     i = backwardMostMove;
10796     linelen = 0;
10797     newblock = TRUE;
10798
10799     while (i < forwardMostMove) {
10800         /* Print comments preceding this move */
10801         if (commentList[i] != NULL) {
10802             if (linelen > 0) fprintf(f, "\n");
10803             fprintf(f, "%s", commentList[i]);
10804             linelen = 0;
10805             newblock = TRUE;
10806         }
10807
10808         /* Format move number */
10809         if ((i % 2) == 0) {
10810             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10811         } else {
10812             if (newblock) {
10813                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10814             } else {
10815                 numtext[0] = NULLCHAR;
10816             }
10817         }
10818         numlen = strlen(numtext);
10819         newblock = FALSE;
10820
10821         /* Print move number */
10822         blank = linelen > 0 && numlen > 0;
10823         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10824             fprintf(f, "\n");
10825             linelen = 0;
10826             blank = 0;
10827         }
10828         if (blank) {
10829             fprintf(f, " ");
10830             linelen++;
10831         }
10832         fprintf(f, "%s", numtext);
10833         linelen += numlen;
10834
10835         /* Get move */
10836         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10837         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10838
10839         /* Print move */
10840         blank = linelen > 0 && movelen > 0;
10841         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10842             fprintf(f, "\n");
10843             linelen = 0;
10844             blank = 0;
10845         }
10846         if (blank) {
10847             fprintf(f, " ");
10848             linelen++;
10849         }
10850         fprintf(f, "%s", move_buffer);
10851         linelen += movelen;
10852
10853         /* [AS] Add PV info if present */
10854         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10855             /* [HGM] add time */
10856             char buf[MSG_SIZ]; int seconds;
10857
10858             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10859
10860             if( seconds <= 0) buf[0] = 0; else
10861             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10862                 seconds = (seconds + 4)/10; // round to full seconds
10863                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10864                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10865             }
10866
10867             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10868                 pvInfoList[i].score >= 0 ? "+" : "",
10869                 pvInfoList[i].score / 100.0,
10870                 pvInfoList[i].depth,
10871                 buf );
10872
10873             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10874
10875             /* Print score/depth */
10876             blank = linelen > 0 && movelen > 0;
10877             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10878                 fprintf(f, "\n");
10879                 linelen = 0;
10880                 blank = 0;
10881             }
10882             if (blank) {
10883                 fprintf(f, " ");
10884                 linelen++;
10885             }
10886             fprintf(f, "%s", move_buffer);
10887             linelen += movelen;
10888         }
10889
10890         i++;
10891     }
10892     
10893     /* Start a new line */
10894     if (linelen > 0) fprintf(f, "\n");
10895
10896     /* Print comments after last move */
10897     if (commentList[i] != NULL) {
10898         fprintf(f, "%s\n", commentList[i]);
10899     }
10900
10901     /* Print result */
10902     if (gameInfo.resultDetails != NULL &&
10903         gameInfo.resultDetails[0] != NULLCHAR) {
10904         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10905                 PGNResult(gameInfo.result));
10906     } else {
10907         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10908     }
10909
10910     fclose(f);
10911     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10912     return TRUE;
10913 }
10914
10915 /* Save game in old style and close the file */
10916 int
10917 SaveGameOldStyle(f)
10918      FILE *f;
10919 {
10920     int i, offset;
10921     time_t tm;
10922     
10923     tm = time((time_t *) NULL);
10924     
10925     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10926     PrintOpponents(f);
10927     
10928     if (backwardMostMove > 0 || startedFromSetupPosition) {
10929         fprintf(f, "\n[--------------\n");
10930         PrintPosition(f, backwardMostMove);
10931         fprintf(f, "--------------]\n");
10932     } else {
10933         fprintf(f, "\n");
10934     }
10935
10936     i = backwardMostMove;
10937     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10938
10939     while (i < forwardMostMove) {
10940         if (commentList[i] != NULL) {
10941             fprintf(f, "[%s]\n", commentList[i]);
10942         }
10943
10944         if ((i % 2) == 1) {
10945             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10946             i++;
10947         } else {
10948             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10949             i++;
10950             if (commentList[i] != NULL) {
10951                 fprintf(f, "\n");
10952                 continue;
10953             }
10954             if (i >= forwardMostMove) {
10955                 fprintf(f, "\n");
10956                 break;
10957             }
10958             fprintf(f, "%s\n", parseList[i]);
10959             i++;
10960         }
10961     }
10962     
10963     if (commentList[i] != NULL) {
10964         fprintf(f, "[%s]\n", commentList[i]);
10965     }
10966
10967     /* This isn't really the old style, but it's close enough */
10968     if (gameInfo.resultDetails != NULL &&
10969         gameInfo.resultDetails[0] != NULLCHAR) {
10970         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10971                 gameInfo.resultDetails);
10972     } else {
10973         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10974     }
10975
10976     fclose(f);
10977     return TRUE;
10978 }
10979
10980 /* Save the current game to open file f and close the file */
10981 int
10982 SaveGame(f, dummy, dummy2)
10983      FILE *f;
10984      int dummy;
10985      char *dummy2;
10986 {
10987     if (gameMode == EditPosition) EditPositionDone(TRUE);
10988     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10989     if (appData.oldSaveStyle)
10990       return SaveGameOldStyle(f);
10991     else
10992       return SaveGamePGN(f);
10993 }
10994
10995 /* Save the current position to the given file */
10996 int
10997 SavePositionToFile(filename)
10998      char *filename;
10999 {
11000     FILE *f;
11001     char buf[MSG_SIZ];
11002
11003     if (strcmp(filename, "-") == 0) {
11004         return SavePosition(stdout, 0, NULL);
11005     } else {
11006         f = fopen(filename, "a");
11007         if (f == NULL) {
11008             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11009             DisplayError(buf, errno);
11010             return FALSE;
11011         } else {
11012             SavePosition(f, 0, NULL);
11013             return TRUE;
11014         }
11015     }
11016 }
11017
11018 /* Save the current position to the given open file and close the file */
11019 int
11020 SavePosition(f, dummy, dummy2)
11021      FILE *f;
11022      int dummy;
11023      char *dummy2;
11024 {
11025     time_t tm;
11026     char *fen;
11027     
11028     if (gameMode == EditPosition) EditPositionDone(TRUE);
11029     if (appData.oldSaveStyle) {
11030         tm = time((time_t *) NULL);
11031     
11032         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11033         PrintOpponents(f);
11034         fprintf(f, "[--------------\n");
11035         PrintPosition(f, currentMove);
11036         fprintf(f, "--------------]\n");
11037     } else {
11038         fen = PositionToFEN(currentMove, NULL);
11039         fprintf(f, "%s\n", fen);
11040         free(fen);
11041     }
11042     fclose(f);
11043     return TRUE;
11044 }
11045
11046 void
11047 ReloadCmailMsgEvent(unregister)
11048      int unregister;
11049 {
11050 #if !WIN32
11051     static char *inFilename = NULL;
11052     static char *outFilename;
11053     int i;
11054     struct stat inbuf, outbuf;
11055     int status;
11056     
11057     /* Any registered moves are unregistered if unregister is set, */
11058     /* i.e. invoked by the signal handler */
11059     if (unregister) {
11060         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11061             cmailMoveRegistered[i] = FALSE;
11062             if (cmailCommentList[i] != NULL) {
11063                 free(cmailCommentList[i]);
11064                 cmailCommentList[i] = NULL;
11065             }
11066         }
11067         nCmailMovesRegistered = 0;
11068     }
11069
11070     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11071         cmailResult[i] = CMAIL_NOT_RESULT;
11072     }
11073     nCmailResults = 0;
11074
11075     if (inFilename == NULL) {
11076         /* Because the filenames are static they only get malloced once  */
11077         /* and they never get freed                                      */
11078         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11079         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11080
11081         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11082         sprintf(outFilename, "%s.out", appData.cmailGameName);
11083     }
11084     
11085     status = stat(outFilename, &outbuf);
11086     if (status < 0) {
11087         cmailMailedMove = FALSE;
11088     } else {
11089         status = stat(inFilename, &inbuf);
11090         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11091     }
11092     
11093     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11094        counts the games, notes how each one terminated, etc.
11095        
11096        It would be nice to remove this kludge and instead gather all
11097        the information while building the game list.  (And to keep it
11098        in the game list nodes instead of having a bunch of fixed-size
11099        parallel arrays.)  Note this will require getting each game's
11100        termination from the PGN tags, as the game list builder does
11101        not process the game moves.  --mann
11102        */
11103     cmailMsgLoaded = TRUE;
11104     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11105     
11106     /* Load first game in the file or popup game menu */
11107     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11108
11109 #endif /* !WIN32 */
11110     return;
11111 }
11112
11113 int
11114 RegisterMove()
11115 {
11116     FILE *f;
11117     char string[MSG_SIZ];
11118
11119     if (   cmailMailedMove
11120         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11121         return TRUE;            /* Allow free viewing  */
11122     }
11123
11124     /* Unregister move to ensure that we don't leave RegisterMove        */
11125     /* with the move registered when the conditions for registering no   */
11126     /* longer hold                                                       */
11127     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11128         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11129         nCmailMovesRegistered --;
11130
11131         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
11132           {
11133               free(cmailCommentList[lastLoadGameNumber - 1]);
11134               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11135           }
11136     }
11137
11138     if (cmailOldMove == -1) {
11139         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11140         return FALSE;
11141     }
11142
11143     if (currentMove > cmailOldMove + 1) {
11144         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11145         return FALSE;
11146     }
11147
11148     if (currentMove < cmailOldMove) {
11149         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11150         return FALSE;
11151     }
11152
11153     if (forwardMostMove > currentMove) {
11154         /* Silently truncate extra moves */
11155         TruncateGame();
11156     }
11157
11158     if (   (currentMove == cmailOldMove + 1)
11159         || (   (currentMove == cmailOldMove)
11160             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11161                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11162         if (gameInfo.result != GameUnfinished) {
11163             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11164         }
11165
11166         if (commentList[currentMove] != NULL) {
11167             cmailCommentList[lastLoadGameNumber - 1]
11168               = StrSave(commentList[currentMove]);
11169         }
11170         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11171
11172         if (appData.debugMode)
11173           fprintf(debugFP, "Saving %s for game %d\n",
11174                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11175
11176         sprintf(string,
11177                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11178         
11179         f = fopen(string, "w");
11180         if (appData.oldSaveStyle) {
11181             SaveGameOldStyle(f); /* also closes the file */
11182             
11183             sprintf(string, "%s.pos.out", appData.cmailGameName);
11184             f = fopen(string, "w");
11185             SavePosition(f, 0, NULL); /* also closes the file */
11186         } else {
11187             fprintf(f, "{--------------\n");
11188             PrintPosition(f, currentMove);
11189             fprintf(f, "--------------}\n\n");
11190             
11191             SaveGame(f, 0, NULL); /* also closes the file*/
11192         }
11193         
11194         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11195         nCmailMovesRegistered ++;
11196     } else if (nCmailGames == 1) {
11197         DisplayError(_("You have not made a move yet"), 0);
11198         return FALSE;
11199     }
11200
11201     return TRUE;
11202 }
11203
11204 void
11205 MailMoveEvent()
11206 {
11207 #if !WIN32
11208     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11209     FILE *commandOutput;
11210     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11211     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11212     int nBuffers;
11213     int i;
11214     int archived;
11215     char *arcDir;
11216
11217     if (! cmailMsgLoaded) {
11218         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11219         return;
11220     }
11221
11222     if (nCmailGames == nCmailResults) {
11223         DisplayError(_("No unfinished games"), 0);
11224         return;
11225     }
11226
11227 #if CMAIL_PROHIBIT_REMAIL
11228     if (cmailMailedMove) {
11229         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);
11230         DisplayError(msg, 0);
11231         return;
11232     }
11233 #endif
11234
11235     if (! (cmailMailedMove || RegisterMove())) return;
11236     
11237     if (   cmailMailedMove
11238         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11239         sprintf(string, partCommandString,
11240                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11241         commandOutput = popen(string, "r");
11242
11243         if (commandOutput == NULL) {
11244             DisplayError(_("Failed to invoke cmail"), 0);
11245         } else {
11246             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11247                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11248             }
11249             if (nBuffers > 1) {
11250                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11251                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11252                 nBytes = MSG_SIZ - 1;
11253             } else {
11254                 (void) memcpy(msg, buffer, nBytes);
11255             }
11256             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11257
11258             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11259                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11260
11261                 archived = TRUE;
11262                 for (i = 0; i < nCmailGames; i ++) {
11263                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11264                         archived = FALSE;
11265                     }
11266                 }
11267                 if (   archived
11268                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11269                         != NULL)) {
11270                     sprintf(buffer, "%s/%s.%s.archive",
11271                             arcDir,
11272                             appData.cmailGameName,
11273                             gameInfo.date);
11274                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11275                     cmailMsgLoaded = FALSE;
11276                 }
11277             }
11278
11279             DisplayInformation(msg);
11280             pclose(commandOutput);
11281         }
11282     } else {
11283         if ((*cmailMsg) != '\0') {
11284             DisplayInformation(cmailMsg);
11285         }
11286     }
11287
11288     return;
11289 #endif /* !WIN32 */
11290 }
11291
11292 char *
11293 CmailMsg()
11294 {
11295 #if WIN32
11296     return NULL;
11297 #else
11298     int  prependComma = 0;
11299     char number[5];
11300     char string[MSG_SIZ];       /* Space for game-list */
11301     int  i;
11302     
11303     if (!cmailMsgLoaded) return "";
11304
11305     if (cmailMailedMove) {
11306         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11307     } else {
11308         /* Create a list of games left */
11309         sprintf(string, "[");
11310         for (i = 0; i < nCmailGames; i ++) {
11311             if (! (   cmailMoveRegistered[i]
11312                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11313                 if (prependComma) {
11314                     sprintf(number, ",%d", i + 1);
11315                 } else {
11316                     sprintf(number, "%d", i + 1);
11317                     prependComma = 1;
11318                 }
11319                 
11320                 strcat(string, number);
11321             }
11322         }
11323         strcat(string, "]");
11324
11325         if (nCmailMovesRegistered + nCmailResults == 0) {
11326             switch (nCmailGames) {
11327               case 1:
11328                 sprintf(cmailMsg,
11329                         _("Still need to make move for game\n"));
11330                 break;
11331                 
11332               case 2:
11333                 sprintf(cmailMsg,
11334                         _("Still need to make moves for both games\n"));
11335                 break;
11336                 
11337               default:
11338                 sprintf(cmailMsg,
11339                         _("Still need to make moves for all %d games\n"),
11340                         nCmailGames);
11341                 break;
11342             }
11343         } else {
11344             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11345               case 1:
11346                 sprintf(cmailMsg,
11347                         _("Still need to make a move for game %s\n"),
11348                         string);
11349                 break;
11350                 
11351               case 0:
11352                 if (nCmailResults == nCmailGames) {
11353                     sprintf(cmailMsg, _("No unfinished games\n"));
11354                 } else {
11355                     sprintf(cmailMsg, _("Ready to send mail\n"));
11356                 }
11357                 break;
11358                 
11359               default:
11360                 sprintf(cmailMsg,
11361                         _("Still need to make moves for games %s\n"),
11362                         string);
11363             }
11364         }
11365     }
11366     return cmailMsg;
11367 #endif /* WIN32 */
11368 }
11369
11370 void
11371 ResetGameEvent()
11372 {
11373     if (gameMode == Training)
11374       SetTrainingModeOff();
11375
11376     Reset(TRUE, TRUE);
11377     cmailMsgLoaded = FALSE;
11378     if (appData.icsActive) {
11379       SendToICS(ics_prefix);
11380       SendToICS("refresh\n");
11381     }
11382 }
11383
11384 void
11385 ExitEvent(status)
11386      int status;
11387 {
11388     exiting++;
11389     if (exiting > 2) {
11390       /* Give up on clean exit */
11391       exit(status);
11392     }
11393     if (exiting > 1) {
11394       /* Keep trying for clean exit */
11395       return;
11396     }
11397
11398     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11399
11400     if (telnetISR != NULL) {
11401       RemoveInputSource(telnetISR);
11402     }
11403     if (icsPR != NoProc) {
11404       DestroyChildProcess(icsPR, TRUE);
11405     }
11406
11407     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11408     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11409
11410     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11411     /* make sure this other one finishes before killing it!                  */
11412     if(endingGame) { int count = 0;
11413         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11414         while(endingGame && count++ < 10) DoSleep(1);
11415         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11416     }
11417
11418     /* Kill off chess programs */
11419     if (first.pr != NoProc) {
11420         ExitAnalyzeMode();
11421         
11422         DoSleep( appData.delayBeforeQuit );
11423         SendToProgram("quit\n", &first);
11424         DoSleep( appData.delayAfterQuit );
11425         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11426     }
11427     if (second.pr != NoProc) {
11428         DoSleep( appData.delayBeforeQuit );
11429         SendToProgram("quit\n", &second);
11430         DoSleep( appData.delayAfterQuit );
11431         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11432     }
11433     if (first.isr != NULL) {
11434         RemoveInputSource(first.isr);
11435     }
11436     if (second.isr != NULL) {
11437         RemoveInputSource(second.isr);
11438     }
11439
11440     ShutDownFrontEnd();
11441     exit(status);
11442 }
11443
11444 void
11445 PauseEvent()
11446 {
11447     if (appData.debugMode)
11448         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11449     if (pausing) {
11450         pausing = FALSE;
11451         ModeHighlight();
11452         if (gameMode == MachinePlaysWhite ||
11453             gameMode == MachinePlaysBlack) {
11454             StartClocks();
11455         } else {
11456             DisplayBothClocks();
11457         }
11458         if (gameMode == PlayFromGameFile) {
11459             if (appData.timeDelay >= 0) 
11460                 AutoPlayGameLoop();
11461         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11462             Reset(FALSE, TRUE);
11463             SendToICS(ics_prefix);
11464             SendToICS("refresh\n");
11465         } else if (currentMove < forwardMostMove) {
11466             ForwardInner(forwardMostMove);
11467         }
11468         pauseExamInvalid = FALSE;
11469     } else {
11470         switch (gameMode) {
11471           default:
11472             return;
11473           case IcsExamining:
11474             pauseExamForwardMostMove = forwardMostMove;
11475             pauseExamInvalid = FALSE;
11476             /* fall through */
11477           case IcsObserving:
11478           case IcsPlayingWhite:
11479           case IcsPlayingBlack:
11480             pausing = TRUE;
11481             ModeHighlight();
11482             return;
11483           case PlayFromGameFile:
11484             (void) StopLoadGameTimer();
11485             pausing = TRUE;
11486             ModeHighlight();
11487             break;
11488           case BeginningOfGame:
11489             if (appData.icsActive) return;
11490             /* else fall through */
11491           case MachinePlaysWhite:
11492           case MachinePlaysBlack:
11493           case TwoMachinesPlay:
11494             if (forwardMostMove == 0)
11495               return;           /* don't pause if no one has moved */
11496             if ((gameMode == MachinePlaysWhite &&
11497                  !WhiteOnMove(forwardMostMove)) ||
11498                 (gameMode == MachinePlaysBlack &&
11499                  WhiteOnMove(forwardMostMove))) {
11500                 StopClocks();
11501             }
11502             pausing = TRUE;
11503             ModeHighlight();
11504             break;
11505         }
11506     }
11507 }
11508
11509 void
11510 EditCommentEvent()
11511 {
11512     char title[MSG_SIZ];
11513
11514     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11515         strcpy(title, _("Edit comment"));
11516     } else {
11517         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11518                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11519                 parseList[currentMove - 1]);
11520     }
11521
11522     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11523 }
11524
11525
11526 void
11527 EditTagsEvent()
11528 {
11529     char *tags = PGNTags(&gameInfo);
11530     EditTagsPopUp(tags);
11531     free(tags);
11532 }
11533
11534 void
11535 AnalyzeModeEvent()
11536 {
11537     if (appData.noChessProgram || gameMode == AnalyzeMode)
11538       return;
11539
11540     if (gameMode != AnalyzeFile) {
11541         if (!appData.icsEngineAnalyze) {
11542                EditGameEvent();
11543                if (gameMode != EditGame) return;
11544         }
11545         ResurrectChessProgram();
11546         SendToProgram("analyze\n", &first);
11547         first.analyzing = TRUE;
11548         /*first.maybeThinking = TRUE;*/
11549         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11550         EngineOutputPopUp();
11551     }
11552     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11553     pausing = FALSE;
11554     ModeHighlight();
11555     SetGameInfo();
11556
11557     StartAnalysisClock();
11558     GetTimeMark(&lastNodeCountTime);
11559     lastNodeCount = 0;
11560 }
11561
11562 void
11563 AnalyzeFileEvent()
11564 {
11565     if (appData.noChessProgram || gameMode == AnalyzeFile)
11566       return;
11567
11568     if (gameMode != AnalyzeMode) {
11569         EditGameEvent();
11570         if (gameMode != EditGame) return;
11571         ResurrectChessProgram();
11572         SendToProgram("analyze\n", &first);
11573         first.analyzing = TRUE;
11574         /*first.maybeThinking = TRUE;*/
11575         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11576         EngineOutputPopUp();
11577     }
11578     gameMode = AnalyzeFile;
11579     pausing = FALSE;
11580     ModeHighlight();
11581     SetGameInfo();
11582
11583     StartAnalysisClock();
11584     GetTimeMark(&lastNodeCountTime);
11585     lastNodeCount = 0;
11586 }
11587
11588 void
11589 MachineWhiteEvent()
11590 {
11591     char buf[MSG_SIZ];
11592     char *bookHit = NULL;
11593
11594     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11595       return;
11596
11597
11598     if (gameMode == PlayFromGameFile || 
11599         gameMode == TwoMachinesPlay  || 
11600         gameMode == Training         || 
11601         gameMode == AnalyzeMode      || 
11602         gameMode == EndOfGame)
11603         EditGameEvent();
11604
11605     if (gameMode == EditPosition) 
11606         EditPositionDone(TRUE);
11607
11608     if (!WhiteOnMove(currentMove)) {
11609         DisplayError(_("It is not White's turn"), 0);
11610         return;
11611     }
11612   
11613     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11614       ExitAnalyzeMode();
11615
11616     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11617         gameMode == AnalyzeFile)
11618         TruncateGame();
11619
11620     ResurrectChessProgram();    /* in case it isn't running */
11621     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11622         gameMode = MachinePlaysWhite;
11623         ResetClocks();
11624     } else
11625     gameMode = MachinePlaysWhite;
11626     pausing = FALSE;
11627     ModeHighlight();
11628     SetGameInfo();
11629     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11630     DisplayTitle(buf);
11631     if (first.sendName) {
11632       sprintf(buf, "name %s\n", gameInfo.black);
11633       SendToProgram(buf, &first);
11634     }
11635     if (first.sendTime) {
11636       if (first.useColors) {
11637         SendToProgram("black\n", &first); /*gnu kludge*/
11638       }
11639       SendTimeRemaining(&first, TRUE);
11640     }
11641     if (first.useColors) {
11642       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11643     }
11644     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11645     SetMachineThinkingEnables();
11646     first.maybeThinking = TRUE;
11647     StartClocks();
11648     firstMove = FALSE;
11649
11650     if (appData.autoFlipView && !flipView) {
11651       flipView = !flipView;
11652       DrawPosition(FALSE, NULL);
11653       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11654     }
11655
11656     if(bookHit) { // [HGM] book: simulate book reply
11657         static char bookMove[MSG_SIZ]; // a bit generous?
11658
11659         programStats.nodes = programStats.depth = programStats.time = 
11660         programStats.score = programStats.got_only_move = 0;
11661         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11662
11663         strcpy(bookMove, "move ");
11664         strcat(bookMove, bookHit);
11665         HandleMachineMove(bookMove, &first);
11666     }
11667 }
11668
11669 void
11670 MachineBlackEvent()
11671 {
11672     char buf[MSG_SIZ];
11673    char *bookHit = NULL;
11674
11675     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11676         return;
11677
11678
11679     if (gameMode == PlayFromGameFile || 
11680         gameMode == TwoMachinesPlay  || 
11681         gameMode == Training         || 
11682         gameMode == AnalyzeMode      || 
11683         gameMode == EndOfGame)
11684         EditGameEvent();
11685
11686     if (gameMode == EditPosition) 
11687         EditPositionDone(TRUE);
11688
11689     if (WhiteOnMove(currentMove)) {
11690         DisplayError(_("It is not Black's turn"), 0);
11691         return;
11692     }
11693     
11694     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11695       ExitAnalyzeMode();
11696
11697     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11698         gameMode == AnalyzeFile)
11699         TruncateGame();
11700
11701     ResurrectChessProgram();    /* in case it isn't running */
11702     gameMode = MachinePlaysBlack;
11703     pausing = FALSE;
11704     ModeHighlight();
11705     SetGameInfo();
11706     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11707     DisplayTitle(buf);
11708     if (first.sendName) {
11709       sprintf(buf, "name %s\n", gameInfo.white);
11710       SendToProgram(buf, &first);
11711     }
11712     if (first.sendTime) {
11713       if (first.useColors) {
11714         SendToProgram("white\n", &first); /*gnu kludge*/
11715       }
11716       SendTimeRemaining(&first, FALSE);
11717     }
11718     if (first.useColors) {
11719       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11720     }
11721     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11722     SetMachineThinkingEnables();
11723     first.maybeThinking = TRUE;
11724     StartClocks();
11725
11726     if (appData.autoFlipView && flipView) {
11727       flipView = !flipView;
11728       DrawPosition(FALSE, NULL);
11729       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11730     }
11731     if(bookHit) { // [HGM] book: simulate book reply
11732         static char bookMove[MSG_SIZ]; // a bit generous?
11733
11734         programStats.nodes = programStats.depth = programStats.time = 
11735         programStats.score = programStats.got_only_move = 0;
11736         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11737
11738         strcpy(bookMove, "move ");
11739         strcat(bookMove, bookHit);
11740         HandleMachineMove(bookMove, &first);
11741     }
11742 }
11743
11744
11745 void
11746 DisplayTwoMachinesTitle()
11747 {
11748     char buf[MSG_SIZ];
11749     if (appData.matchGames > 0) {
11750         if (first.twoMachinesColor[0] == 'w') {
11751             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11752                     gameInfo.white, gameInfo.black,
11753                     first.matchWins, second.matchWins,
11754                     matchGame - 1 - (first.matchWins + second.matchWins));
11755         } else {
11756             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11757                     gameInfo.white, gameInfo.black,
11758                     second.matchWins, first.matchWins,
11759                     matchGame - 1 - (first.matchWins + second.matchWins));
11760         }
11761     } else {
11762         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11763     }
11764     DisplayTitle(buf);
11765 }
11766
11767 void
11768 TwoMachinesEvent P((void))
11769 {
11770     int i;
11771     char buf[MSG_SIZ];
11772     ChessProgramState *onmove;
11773     char *bookHit = NULL;
11774     
11775     if (appData.noChessProgram) return;
11776
11777     switch (gameMode) {
11778       case TwoMachinesPlay:
11779         return;
11780       case MachinePlaysWhite:
11781       case MachinePlaysBlack:
11782         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11783             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11784             return;
11785         }
11786         /* fall through */
11787       case BeginningOfGame:
11788       case PlayFromGameFile:
11789       case EndOfGame:
11790         EditGameEvent();
11791         if (gameMode != EditGame) return;
11792         break;
11793       case EditPosition:
11794         EditPositionDone(TRUE);
11795         break;
11796       case AnalyzeMode:
11797       case AnalyzeFile:
11798         ExitAnalyzeMode();
11799         break;
11800       case EditGame:
11801       default:
11802         break;
11803     }
11804
11805 //    forwardMostMove = currentMove;
11806     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11807     ResurrectChessProgram();    /* in case first program isn't running */
11808
11809     if (second.pr == NULL) {
11810         StartChessProgram(&second);
11811         if (second.protocolVersion == 1) {
11812           TwoMachinesEventIfReady();
11813         } else {
11814           /* kludge: allow timeout for initial "feature" command */
11815           FreezeUI();
11816           DisplayMessage("", _("Starting second chess program"));
11817           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11818         }
11819         return;
11820     }
11821     DisplayMessage("", "");
11822     InitChessProgram(&second, FALSE);
11823     SendToProgram("force\n", &second);
11824     if (startedFromSetupPosition) {
11825         SendBoard(&second, backwardMostMove);
11826     if (appData.debugMode) {
11827         fprintf(debugFP, "Two Machines\n");
11828     }
11829     }
11830     for (i = backwardMostMove; i < forwardMostMove; i++) {
11831         SendMoveToProgram(i, &second);
11832     }
11833
11834     gameMode = TwoMachinesPlay;
11835     pausing = FALSE;
11836     ModeHighlight();
11837     SetGameInfo();
11838     DisplayTwoMachinesTitle();
11839     firstMove = TRUE;
11840     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11841         onmove = &first;
11842     } else {
11843         onmove = &second;
11844     }
11845
11846     SendToProgram(first.computerString, &first);
11847     if (first.sendName) {
11848       sprintf(buf, "name %s\n", second.tidy);
11849       SendToProgram(buf, &first);
11850     }
11851     SendToProgram(second.computerString, &second);
11852     if (second.sendName) {
11853       sprintf(buf, "name %s\n", first.tidy);
11854       SendToProgram(buf, &second);
11855     }
11856
11857     ResetClocks();
11858     if (!first.sendTime || !second.sendTime) {
11859         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11860         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11861     }
11862     if (onmove->sendTime) {
11863       if (onmove->useColors) {
11864         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11865       }
11866       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11867     }
11868     if (onmove->useColors) {
11869       SendToProgram(onmove->twoMachinesColor, onmove);
11870     }
11871     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11872 //    SendToProgram("go\n", onmove);
11873     onmove->maybeThinking = TRUE;
11874     SetMachineThinkingEnables();
11875
11876     StartClocks();
11877
11878     if(bookHit) { // [HGM] book: simulate book reply
11879         static char bookMove[MSG_SIZ]; // a bit generous?
11880
11881         programStats.nodes = programStats.depth = programStats.time = 
11882         programStats.score = programStats.got_only_move = 0;
11883         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11884
11885         strcpy(bookMove, "move ");
11886         strcat(bookMove, bookHit);
11887         savedMessage = bookMove; // args for deferred call
11888         savedState = onmove;
11889         ScheduleDelayedEvent(DeferredBookMove, 1);
11890     }
11891 }
11892
11893 void
11894 TrainingEvent()
11895 {
11896     if (gameMode == Training) {
11897       SetTrainingModeOff();
11898       gameMode = PlayFromGameFile;
11899       DisplayMessage("", _("Training mode off"));
11900     } else {
11901       gameMode = Training;
11902       animateTraining = appData.animate;
11903
11904       /* make sure we are not already at the end of the game */
11905       if (currentMove < forwardMostMove) {
11906         SetTrainingModeOn();
11907         DisplayMessage("", _("Training mode on"));
11908       } else {
11909         gameMode = PlayFromGameFile;
11910         DisplayError(_("Already at end of game"), 0);
11911       }
11912     }
11913     ModeHighlight();
11914 }
11915
11916 void
11917 IcsClientEvent()
11918 {
11919     if (!appData.icsActive) return;
11920     switch (gameMode) {
11921       case IcsPlayingWhite:
11922       case IcsPlayingBlack:
11923       case IcsObserving:
11924       case IcsIdle:
11925       case BeginningOfGame:
11926       case IcsExamining:
11927         return;
11928
11929       case EditGame:
11930         break;
11931
11932       case EditPosition:
11933         EditPositionDone(TRUE);
11934         break;
11935
11936       case AnalyzeMode:
11937       case AnalyzeFile:
11938         ExitAnalyzeMode();
11939         break;
11940         
11941       default:
11942         EditGameEvent();
11943         break;
11944     }
11945
11946     gameMode = IcsIdle;
11947     ModeHighlight();
11948     return;
11949 }
11950
11951
11952 void
11953 EditGameEvent()
11954 {
11955     int i;
11956
11957     switch (gameMode) {
11958       case Training:
11959         SetTrainingModeOff();
11960         break;
11961       case MachinePlaysWhite:
11962       case MachinePlaysBlack:
11963       case BeginningOfGame:
11964         SendToProgram("force\n", &first);
11965         SetUserThinkingEnables();
11966         break;
11967       case PlayFromGameFile:
11968         (void) StopLoadGameTimer();
11969         if (gameFileFP != NULL) {
11970             gameFileFP = NULL;
11971         }
11972         break;
11973       case EditPosition:
11974         EditPositionDone(TRUE);
11975         break;
11976       case AnalyzeMode:
11977       case AnalyzeFile:
11978         ExitAnalyzeMode();
11979         SendToProgram("force\n", &first);
11980         break;
11981       case TwoMachinesPlay:
11982         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11983         ResurrectChessProgram();
11984         SetUserThinkingEnables();
11985         break;
11986       case EndOfGame:
11987         ResurrectChessProgram();
11988         break;
11989       case IcsPlayingBlack:
11990       case IcsPlayingWhite:
11991         DisplayError(_("Warning: You are still playing a game"), 0);
11992         break;
11993       case IcsObserving:
11994         DisplayError(_("Warning: You are still observing a game"), 0);
11995         break;
11996       case IcsExamining:
11997         DisplayError(_("Warning: You are still examining a game"), 0);
11998         break;
11999       case IcsIdle:
12000         break;
12001       case EditGame:
12002       default:
12003         return;
12004     }
12005     
12006     pausing = FALSE;
12007     StopClocks();
12008     first.offeredDraw = second.offeredDraw = 0;
12009
12010     if (gameMode == PlayFromGameFile) {
12011         whiteTimeRemaining = timeRemaining[0][currentMove];
12012         blackTimeRemaining = timeRemaining[1][currentMove];
12013         DisplayTitle("");
12014     }
12015
12016     if (gameMode == MachinePlaysWhite ||
12017         gameMode == MachinePlaysBlack ||
12018         gameMode == TwoMachinesPlay ||
12019         gameMode == EndOfGame) {
12020         i = forwardMostMove;
12021         while (i > currentMove) {
12022             SendToProgram("undo\n", &first);
12023             i--;
12024         }
12025         whiteTimeRemaining = timeRemaining[0][currentMove];
12026         blackTimeRemaining = timeRemaining[1][currentMove];
12027         DisplayBothClocks();
12028         if (whiteFlag || blackFlag) {
12029             whiteFlag = blackFlag = 0;
12030         }
12031         DisplayTitle("");
12032     }           
12033     
12034     gameMode = EditGame;
12035     ModeHighlight();
12036     SetGameInfo();
12037 }
12038
12039
12040 void
12041 EditPositionEvent()
12042 {
12043     if (gameMode == EditPosition) {
12044         EditGameEvent();
12045         return;
12046     }
12047     
12048     EditGameEvent();
12049     if (gameMode != EditGame) return;
12050     
12051     gameMode = EditPosition;
12052     ModeHighlight();
12053     SetGameInfo();
12054     if (currentMove > 0)
12055       CopyBoard(boards[0], boards[currentMove]);
12056     
12057     blackPlaysFirst = !WhiteOnMove(currentMove);
12058     ResetClocks();
12059     currentMove = forwardMostMove = backwardMostMove = 0;
12060     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12061     DisplayMove(-1);
12062 }
12063
12064 void
12065 ExitAnalyzeMode()
12066 {
12067     /* [DM] icsEngineAnalyze - possible call from other functions */
12068     if (appData.icsEngineAnalyze) {
12069         appData.icsEngineAnalyze = FALSE;
12070
12071         DisplayMessage("",_("Close ICS engine analyze..."));
12072     }
12073     if (first.analysisSupport && first.analyzing) {
12074       SendToProgram("exit\n", &first);
12075       first.analyzing = FALSE;
12076     }
12077     thinkOutput[0] = NULLCHAR;
12078 }
12079
12080 void
12081 EditPositionDone(Boolean fakeRights)
12082 {
12083     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12084
12085     startedFromSetupPosition = TRUE;
12086     InitChessProgram(&first, FALSE);
12087     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12088       boards[0][EP_STATUS] = EP_NONE;
12089       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12090     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12091         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12092         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12093       } else boards[0][CASTLING][2] = NoRights;
12094     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12095         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12096         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12097       } else boards[0][CASTLING][5] = NoRights;
12098     }
12099     SendToProgram("force\n", &first);
12100     if (blackPlaysFirst) {
12101         strcpy(moveList[0], "");
12102         strcpy(parseList[0], "");
12103         currentMove = forwardMostMove = backwardMostMove = 1;
12104         CopyBoard(boards[1], boards[0]);
12105     } else {
12106         currentMove = forwardMostMove = backwardMostMove = 0;
12107     }
12108     SendBoard(&first, forwardMostMove);
12109     if (appData.debugMode) {
12110         fprintf(debugFP, "EditPosDone\n");
12111     }
12112     DisplayTitle("");
12113     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12114     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12115     gameMode = EditGame;
12116     ModeHighlight();
12117     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12118     ClearHighlights(); /* [AS] */
12119 }
12120
12121 /* Pause for `ms' milliseconds */
12122 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12123 void
12124 TimeDelay(ms)
12125      long ms;
12126 {
12127     TimeMark m1, m2;
12128
12129     GetTimeMark(&m1);
12130     do {
12131         GetTimeMark(&m2);
12132     } while (SubtractTimeMarks(&m2, &m1) < ms);
12133 }
12134
12135 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12136 void
12137 SendMultiLineToICS(buf)
12138      char *buf;
12139 {
12140     char temp[MSG_SIZ+1], *p;
12141     int len;
12142
12143     len = strlen(buf);
12144     if (len > MSG_SIZ)
12145       len = MSG_SIZ;
12146   
12147     strncpy(temp, buf, len);
12148     temp[len] = 0;
12149
12150     p = temp;
12151     while (*p) {
12152         if (*p == '\n' || *p == '\r')
12153           *p = ' ';
12154         ++p;
12155     }
12156
12157     strcat(temp, "\n");
12158     SendToICS(temp);
12159     SendToPlayer(temp, strlen(temp));
12160 }
12161
12162 void
12163 SetWhiteToPlayEvent()
12164 {
12165     if (gameMode == EditPosition) {
12166         blackPlaysFirst = FALSE;
12167         DisplayBothClocks();    /* works because currentMove is 0 */
12168     } else if (gameMode == IcsExamining) {
12169         SendToICS(ics_prefix);
12170         SendToICS("tomove white\n");
12171     }
12172 }
12173
12174 void
12175 SetBlackToPlayEvent()
12176 {
12177     if (gameMode == EditPosition) {
12178         blackPlaysFirst = TRUE;
12179         currentMove = 1;        /* kludge */
12180         DisplayBothClocks();
12181         currentMove = 0;
12182     } else if (gameMode == IcsExamining) {
12183         SendToICS(ics_prefix);
12184         SendToICS("tomove black\n");
12185     }
12186 }
12187
12188 void
12189 EditPositionMenuEvent(selection, x, y)
12190      ChessSquare selection;
12191      int x, y;
12192 {
12193     char buf[MSG_SIZ];
12194     ChessSquare piece = boards[0][y][x];
12195
12196     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12197
12198     switch (selection) {
12199       case ClearBoard:
12200         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12201             SendToICS(ics_prefix);
12202             SendToICS("bsetup clear\n");
12203         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12204             SendToICS(ics_prefix);
12205             SendToICS("clearboard\n");
12206         } else {
12207             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12208                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12209                 for (y = 0; y < BOARD_HEIGHT; y++) {
12210                     if (gameMode == IcsExamining) {
12211                         if (boards[currentMove][y][x] != EmptySquare) {
12212                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
12213                                     AAA + x, ONE + y);
12214                             SendToICS(buf);
12215                         }
12216                     } else {
12217                         boards[0][y][x] = p;
12218                     }
12219                 }
12220             }
12221         }
12222         if (gameMode == EditPosition) {
12223             DrawPosition(FALSE, boards[0]);
12224         }
12225         break;
12226
12227       case WhitePlay:
12228         SetWhiteToPlayEvent();
12229         break;
12230
12231       case BlackPlay:
12232         SetBlackToPlayEvent();
12233         break;
12234
12235       case EmptySquare:
12236         if (gameMode == IcsExamining) {
12237             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12238             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12239             SendToICS(buf);
12240         } else {
12241             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12242                 if(x == BOARD_LEFT-2) {
12243                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12244                     boards[0][y][1] = 0;
12245                 } else
12246                 if(x == BOARD_RGHT+1) {
12247                     if(y >= gameInfo.holdingsSize) break;
12248                     boards[0][y][BOARD_WIDTH-2] = 0;
12249                 } else break;
12250             }
12251             boards[0][y][x] = EmptySquare;
12252             DrawPosition(FALSE, boards[0]);
12253         }
12254         break;
12255
12256       case PromotePiece:
12257         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12258            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12259             selection = (ChessSquare) (PROMOTED piece);
12260         } else if(piece == EmptySquare) selection = WhiteSilver;
12261         else selection = (ChessSquare)((int)piece - 1);
12262         goto defaultlabel;
12263
12264       case DemotePiece:
12265         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12266            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12267             selection = (ChessSquare) (DEMOTED piece);
12268         } else if(piece == EmptySquare) selection = BlackSilver;
12269         else selection = (ChessSquare)((int)piece + 1);       
12270         goto defaultlabel;
12271
12272       case WhiteQueen:
12273       case BlackQueen:
12274         if(gameInfo.variant == VariantShatranj ||
12275            gameInfo.variant == VariantXiangqi  ||
12276            gameInfo.variant == VariantCourier  ||
12277            gameInfo.variant == VariantMakruk     )
12278             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12279         goto defaultlabel;
12280
12281       case WhiteKing:
12282       case BlackKing:
12283         if(gameInfo.variant == VariantXiangqi)
12284             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12285         if(gameInfo.variant == VariantKnightmate)
12286             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12287       default:
12288         defaultlabel:
12289         if (gameMode == IcsExamining) {
12290             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12291             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12292                     PieceToChar(selection), AAA + x, ONE + y);
12293             SendToICS(buf);
12294         } else {
12295             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12296                 int n;
12297                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12298                     n = PieceToNumber(selection - BlackPawn);
12299                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12300                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12301                     boards[0][BOARD_HEIGHT-1-n][1]++;
12302                 } else
12303                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12304                     n = PieceToNumber(selection);
12305                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12306                     boards[0][n][BOARD_WIDTH-1] = selection;
12307                     boards[0][n][BOARD_WIDTH-2]++;
12308                 }
12309             } else
12310             boards[0][y][x] = selection;
12311             DrawPosition(TRUE, boards[0]);
12312         }
12313         break;
12314     }
12315 }
12316
12317
12318 void
12319 DropMenuEvent(selection, x, y)
12320      ChessSquare selection;
12321      int x, y;
12322 {
12323     ChessMove moveType;
12324
12325     switch (gameMode) {
12326       case IcsPlayingWhite:
12327       case MachinePlaysBlack:
12328         if (!WhiteOnMove(currentMove)) {
12329             DisplayMoveError(_("It is Black's turn"));
12330             return;
12331         }
12332         moveType = WhiteDrop;
12333         break;
12334       case IcsPlayingBlack:
12335       case MachinePlaysWhite:
12336         if (WhiteOnMove(currentMove)) {
12337             DisplayMoveError(_("It is White's turn"));
12338             return;
12339         }
12340         moveType = BlackDrop;
12341         break;
12342       case EditGame:
12343         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12344         break;
12345       default:
12346         return;
12347     }
12348
12349     if (moveType == BlackDrop && selection < BlackPawn) {
12350       selection = (ChessSquare) ((int) selection
12351                                  + (int) BlackPawn - (int) WhitePawn);
12352     }
12353     if (boards[currentMove][y][x] != EmptySquare) {
12354         DisplayMoveError(_("That square is occupied"));
12355         return;
12356     }
12357
12358     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12359 }
12360
12361 void
12362 AcceptEvent()
12363 {
12364     /* Accept a pending offer of any kind from opponent */
12365     
12366     if (appData.icsActive) {
12367         SendToICS(ics_prefix);
12368         SendToICS("accept\n");
12369     } else if (cmailMsgLoaded) {
12370         if (currentMove == cmailOldMove &&
12371             commentList[cmailOldMove] != NULL &&
12372             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12373                    "Black offers a draw" : "White offers a draw")) {
12374             TruncateGame();
12375             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12376             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12377         } else {
12378             DisplayError(_("There is no pending offer on this move"), 0);
12379             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12380         }
12381     } else {
12382         /* Not used for offers from chess program */
12383     }
12384 }
12385
12386 void
12387 DeclineEvent()
12388 {
12389     /* Decline a pending offer of any kind from opponent */
12390     
12391     if (appData.icsActive) {
12392         SendToICS(ics_prefix);
12393         SendToICS("decline\n");
12394     } else if (cmailMsgLoaded) {
12395         if (currentMove == cmailOldMove &&
12396             commentList[cmailOldMove] != NULL &&
12397             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12398                    "Black offers a draw" : "White offers a draw")) {
12399 #ifdef NOTDEF
12400             AppendComment(cmailOldMove, "Draw declined", TRUE);
12401             DisplayComment(cmailOldMove - 1, "Draw declined");
12402 #endif /*NOTDEF*/
12403         } else {
12404             DisplayError(_("There is no pending offer on this move"), 0);
12405         }
12406     } else {
12407         /* Not used for offers from chess program */
12408     }
12409 }
12410
12411 void
12412 RematchEvent()
12413 {
12414     /* Issue ICS rematch command */
12415     if (appData.icsActive) {
12416         SendToICS(ics_prefix);
12417         SendToICS("rematch\n");
12418     }
12419 }
12420
12421 void
12422 CallFlagEvent()
12423 {
12424     /* Call your opponent's flag (claim a win on time) */
12425     if (appData.icsActive) {
12426         SendToICS(ics_prefix);
12427         SendToICS("flag\n");
12428     } else {
12429         switch (gameMode) {
12430           default:
12431             return;
12432           case MachinePlaysWhite:
12433             if (whiteFlag) {
12434                 if (blackFlag)
12435                   GameEnds(GameIsDrawn, "Both players ran out of time",
12436                            GE_PLAYER);
12437                 else
12438                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12439             } else {
12440                 DisplayError(_("Your opponent is not out of time"), 0);
12441             }
12442             break;
12443           case MachinePlaysBlack:
12444             if (blackFlag) {
12445                 if (whiteFlag)
12446                   GameEnds(GameIsDrawn, "Both players ran out of time",
12447                            GE_PLAYER);
12448                 else
12449                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12450             } else {
12451                 DisplayError(_("Your opponent is not out of time"), 0);
12452             }
12453             break;
12454         }
12455     }
12456 }
12457
12458 void
12459 DrawEvent()
12460 {
12461     /* Offer draw or accept pending draw offer from opponent */
12462     
12463     if (appData.icsActive) {
12464         /* Note: tournament rules require draw offers to be
12465            made after you make your move but before you punch
12466            your clock.  Currently ICS doesn't let you do that;
12467            instead, you immediately punch your clock after making
12468            a move, but you can offer a draw at any time. */
12469         
12470         SendToICS(ics_prefix);
12471         SendToICS("draw\n");
12472         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12473     } else if (cmailMsgLoaded) {
12474         if (currentMove == cmailOldMove &&
12475             commentList[cmailOldMove] != NULL &&
12476             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12477                    "Black offers a draw" : "White offers a draw")) {
12478             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12479             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12480         } else if (currentMove == cmailOldMove + 1) {
12481             char *offer = WhiteOnMove(cmailOldMove) ?
12482               "White offers a draw" : "Black offers a draw";
12483             AppendComment(currentMove, offer, TRUE);
12484             DisplayComment(currentMove - 1, offer);
12485             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12486         } else {
12487             DisplayError(_("You must make your move before offering a draw"), 0);
12488             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12489         }
12490     } else if (first.offeredDraw) {
12491         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12492     } else {
12493         if (first.sendDrawOffers) {
12494             SendToProgram("draw\n", &first);
12495             userOfferedDraw = TRUE;
12496         }
12497     }
12498 }
12499
12500 void
12501 AdjournEvent()
12502 {
12503     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12504     
12505     if (appData.icsActive) {
12506         SendToICS(ics_prefix);
12507         SendToICS("adjourn\n");
12508     } else {
12509         /* Currently GNU Chess doesn't offer or accept Adjourns */
12510     }
12511 }
12512
12513
12514 void
12515 AbortEvent()
12516 {
12517     /* Offer Abort or accept pending Abort offer from opponent */
12518     
12519     if (appData.icsActive) {
12520         SendToICS(ics_prefix);
12521         SendToICS("abort\n");
12522     } else {
12523         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12524     }
12525 }
12526
12527 void
12528 ResignEvent()
12529 {
12530     /* Resign.  You can do this even if it's not your turn. */
12531     
12532     if (appData.icsActive) {
12533         SendToICS(ics_prefix);
12534         SendToICS("resign\n");
12535     } else {
12536         switch (gameMode) {
12537           case MachinePlaysWhite:
12538             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12539             break;
12540           case MachinePlaysBlack:
12541             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12542             break;
12543           case EditGame:
12544             if (cmailMsgLoaded) {
12545                 TruncateGame();
12546                 if (WhiteOnMove(cmailOldMove)) {
12547                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12548                 } else {
12549                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12550                 }
12551                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12552             }
12553             break;
12554           default:
12555             break;
12556         }
12557     }
12558 }
12559
12560
12561 void
12562 StopObservingEvent()
12563 {
12564     /* Stop observing current games */
12565     SendToICS(ics_prefix);
12566     SendToICS("unobserve\n");
12567 }
12568
12569 void
12570 StopExaminingEvent()
12571 {
12572     /* Stop observing current game */
12573     SendToICS(ics_prefix);
12574     SendToICS("unexamine\n");
12575 }
12576
12577 void
12578 ForwardInner(target)
12579      int target;
12580 {
12581     int limit;
12582
12583     if (appData.debugMode)
12584         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12585                 target, currentMove, forwardMostMove);
12586
12587     if (gameMode == EditPosition)
12588       return;
12589
12590     if (gameMode == PlayFromGameFile && !pausing)
12591       PauseEvent();
12592     
12593     if (gameMode == IcsExamining && pausing)
12594       limit = pauseExamForwardMostMove;
12595     else
12596       limit = forwardMostMove;
12597     
12598     if (target > limit) target = limit;
12599
12600     if (target > 0 && moveList[target - 1][0]) {
12601         int fromX, fromY, toX, toY;
12602         toX = moveList[target - 1][2] - AAA;
12603         toY = moveList[target - 1][3] - ONE;
12604         if (moveList[target - 1][1] == '@') {
12605             if (appData.highlightLastMove) {
12606                 SetHighlights(-1, -1, toX, toY);
12607             }
12608         } else {
12609             fromX = moveList[target - 1][0] - AAA;
12610             fromY = moveList[target - 1][1] - ONE;
12611             if (target == currentMove + 1) {
12612                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12613             }
12614             if (appData.highlightLastMove) {
12615                 SetHighlights(fromX, fromY, toX, toY);
12616             }
12617         }
12618     }
12619     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12620         gameMode == Training || gameMode == PlayFromGameFile || 
12621         gameMode == AnalyzeFile) {
12622         while (currentMove < target) {
12623             SendMoveToProgram(currentMove++, &first);
12624         }
12625     } else {
12626         currentMove = target;
12627     }
12628     
12629     if (gameMode == EditGame || gameMode == EndOfGame) {
12630         whiteTimeRemaining = timeRemaining[0][currentMove];
12631         blackTimeRemaining = timeRemaining[1][currentMove];
12632     }
12633     DisplayBothClocks();
12634     DisplayMove(currentMove - 1);
12635     DrawPosition(FALSE, boards[currentMove]);
12636     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12637     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12638         DisplayComment(currentMove - 1, commentList[currentMove]);
12639     }
12640 }
12641
12642
12643 void
12644 ForwardEvent()
12645 {
12646     if (gameMode == IcsExamining && !pausing) {
12647         SendToICS(ics_prefix);
12648         SendToICS("forward\n");
12649     } else {
12650         ForwardInner(currentMove + 1);
12651     }
12652 }
12653
12654 void
12655 ToEndEvent()
12656 {
12657     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12658         /* to optimze, we temporarily turn off analysis mode while we feed
12659          * the remaining moves to the engine. Otherwise we get analysis output
12660          * after each move.
12661          */ 
12662         if (first.analysisSupport) {
12663           SendToProgram("exit\nforce\n", &first);
12664           first.analyzing = FALSE;
12665         }
12666     }
12667         
12668     if (gameMode == IcsExamining && !pausing) {
12669         SendToICS(ics_prefix);
12670         SendToICS("forward 999999\n");
12671     } else {
12672         ForwardInner(forwardMostMove);
12673     }
12674
12675     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12676         /* we have fed all the moves, so reactivate analysis mode */
12677         SendToProgram("analyze\n", &first);
12678         first.analyzing = TRUE;
12679         /*first.maybeThinking = TRUE;*/
12680         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12681     }
12682 }
12683
12684 void
12685 BackwardInner(target)
12686      int target;
12687 {
12688     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12689
12690     if (appData.debugMode)
12691         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12692                 target, currentMove, forwardMostMove);
12693
12694     if (gameMode == EditPosition) return;
12695     if (currentMove <= backwardMostMove) {
12696         ClearHighlights();
12697         DrawPosition(full_redraw, boards[currentMove]);
12698         return;
12699     }
12700     if (gameMode == PlayFromGameFile && !pausing)
12701       PauseEvent();
12702     
12703     if (moveList[target][0]) {
12704         int fromX, fromY, toX, toY;
12705         toX = moveList[target][2] - AAA;
12706         toY = moveList[target][3] - ONE;
12707         if (moveList[target][1] == '@') {
12708             if (appData.highlightLastMove) {
12709                 SetHighlights(-1, -1, toX, toY);
12710             }
12711         } else {
12712             fromX = moveList[target][0] - AAA;
12713             fromY = moveList[target][1] - ONE;
12714             if (target == currentMove - 1) {
12715                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12716             }
12717             if (appData.highlightLastMove) {
12718                 SetHighlights(fromX, fromY, toX, toY);
12719             }
12720         }
12721     }
12722     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12723         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12724         while (currentMove > target) {
12725             SendToProgram("undo\n", &first);
12726             currentMove--;
12727         }
12728     } else {
12729         currentMove = target;
12730     }
12731     
12732     if (gameMode == EditGame || gameMode == EndOfGame) {
12733         whiteTimeRemaining = timeRemaining[0][currentMove];
12734         blackTimeRemaining = timeRemaining[1][currentMove];
12735     }
12736     DisplayBothClocks();
12737     DisplayMove(currentMove - 1);
12738     DrawPosition(full_redraw, boards[currentMove]);
12739     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12740     // [HGM] PV info: routine tests if comment empty
12741     DisplayComment(currentMove - 1, commentList[currentMove]);
12742 }
12743
12744 void
12745 BackwardEvent()
12746 {
12747     if (gameMode == IcsExamining && !pausing) {
12748         SendToICS(ics_prefix);
12749         SendToICS("backward\n");
12750     } else {
12751         BackwardInner(currentMove - 1);
12752     }
12753 }
12754
12755 void
12756 ToStartEvent()
12757 {
12758     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12759         /* to optimize, we temporarily turn off analysis mode while we undo
12760          * all the moves. Otherwise we get analysis output after each undo.
12761          */ 
12762         if (first.analysisSupport) {
12763           SendToProgram("exit\nforce\n", &first);
12764           first.analyzing = FALSE;
12765         }
12766     }
12767
12768     if (gameMode == IcsExamining && !pausing) {
12769         SendToICS(ics_prefix);
12770         SendToICS("backward 999999\n");
12771     } else {
12772         BackwardInner(backwardMostMove);
12773     }
12774
12775     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12776         /* we have fed all the moves, so reactivate analysis mode */
12777         SendToProgram("analyze\n", &first);
12778         first.analyzing = TRUE;
12779         /*first.maybeThinking = TRUE;*/
12780         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12781     }
12782 }
12783
12784 void
12785 ToNrEvent(int to)
12786 {
12787   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12788   if (to >= forwardMostMove) to = forwardMostMove;
12789   if (to <= backwardMostMove) to = backwardMostMove;
12790   if (to < currentMove) {
12791     BackwardInner(to);
12792   } else {
12793     ForwardInner(to);
12794   }
12795 }
12796
12797 void
12798 RevertEvent(Boolean annotate)
12799 {
12800     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12801         return;
12802     }
12803     if (gameMode != IcsExamining) {
12804         DisplayError(_("You are not examining a game"), 0);
12805         return;
12806     }
12807     if (pausing) {
12808         DisplayError(_("You can't revert while pausing"), 0);
12809         return;
12810     }
12811     SendToICS(ics_prefix);
12812     SendToICS("revert\n");
12813 }
12814
12815 void
12816 RetractMoveEvent()
12817 {
12818     switch (gameMode) {
12819       case MachinePlaysWhite:
12820       case MachinePlaysBlack:
12821         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12822             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12823             return;
12824         }
12825         if (forwardMostMove < 2) return;
12826         currentMove = forwardMostMove = forwardMostMove - 2;
12827         whiteTimeRemaining = timeRemaining[0][currentMove];
12828         blackTimeRemaining = timeRemaining[1][currentMove];
12829         DisplayBothClocks();
12830         DisplayMove(currentMove - 1);
12831         ClearHighlights();/*!! could figure this out*/
12832         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12833         SendToProgram("remove\n", &first);
12834         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12835         break;
12836
12837       case BeginningOfGame:
12838       default:
12839         break;
12840
12841       case IcsPlayingWhite:
12842       case IcsPlayingBlack:
12843         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12844             SendToICS(ics_prefix);
12845             SendToICS("takeback 2\n");
12846         } else {
12847             SendToICS(ics_prefix);
12848             SendToICS("takeback 1\n");
12849         }
12850         break;
12851     }
12852 }
12853
12854 void
12855 MoveNowEvent()
12856 {
12857     ChessProgramState *cps;
12858
12859     switch (gameMode) {
12860       case MachinePlaysWhite:
12861         if (!WhiteOnMove(forwardMostMove)) {
12862             DisplayError(_("It is your turn"), 0);
12863             return;
12864         }
12865         cps = &first;
12866         break;
12867       case MachinePlaysBlack:
12868         if (WhiteOnMove(forwardMostMove)) {
12869             DisplayError(_("It is your turn"), 0);
12870             return;
12871         }
12872         cps = &first;
12873         break;
12874       case TwoMachinesPlay:
12875         if (WhiteOnMove(forwardMostMove) ==
12876             (first.twoMachinesColor[0] == 'w')) {
12877             cps = &first;
12878         } else {
12879             cps = &second;
12880         }
12881         break;
12882       case BeginningOfGame:
12883       default:
12884         return;
12885     }
12886     SendToProgram("?\n", cps);
12887 }
12888
12889 void
12890 TruncateGameEvent()
12891 {
12892     EditGameEvent();
12893     if (gameMode != EditGame) return;
12894     TruncateGame();
12895 }
12896
12897 void
12898 TruncateGame()
12899 {
12900     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12901     if (forwardMostMove > currentMove) {
12902         if (gameInfo.resultDetails != NULL) {
12903             free(gameInfo.resultDetails);
12904             gameInfo.resultDetails = NULL;
12905             gameInfo.result = GameUnfinished;
12906         }
12907         forwardMostMove = currentMove;
12908         HistorySet(parseList, backwardMostMove, forwardMostMove,
12909                    currentMove-1);
12910     }
12911 }
12912
12913 void
12914 HintEvent()
12915 {
12916     if (appData.noChessProgram) return;
12917     switch (gameMode) {
12918       case MachinePlaysWhite:
12919         if (WhiteOnMove(forwardMostMove)) {
12920             DisplayError(_("Wait until your turn"), 0);
12921             return;
12922         }
12923         break;
12924       case BeginningOfGame:
12925       case MachinePlaysBlack:
12926         if (!WhiteOnMove(forwardMostMove)) {
12927             DisplayError(_("Wait until your turn"), 0);
12928             return;
12929         }
12930         break;
12931       default:
12932         DisplayError(_("No hint available"), 0);
12933         return;
12934     }
12935     SendToProgram("hint\n", &first);
12936     hintRequested = TRUE;
12937 }
12938
12939 void
12940 BookEvent()
12941 {
12942     if (appData.noChessProgram) return;
12943     switch (gameMode) {
12944       case MachinePlaysWhite:
12945         if (WhiteOnMove(forwardMostMove)) {
12946             DisplayError(_("Wait until your turn"), 0);
12947             return;
12948         }
12949         break;
12950       case BeginningOfGame:
12951       case MachinePlaysBlack:
12952         if (!WhiteOnMove(forwardMostMove)) {
12953             DisplayError(_("Wait until your turn"), 0);
12954             return;
12955         }
12956         break;
12957       case EditPosition:
12958         EditPositionDone(TRUE);
12959         break;
12960       case TwoMachinesPlay:
12961         return;
12962       default:
12963         break;
12964     }
12965     SendToProgram("bk\n", &first);
12966     bookOutput[0] = NULLCHAR;
12967     bookRequested = TRUE;
12968 }
12969
12970 void
12971 AboutGameEvent()
12972 {
12973     char *tags = PGNTags(&gameInfo);
12974     TagsPopUp(tags, CmailMsg());
12975     free(tags);
12976 }
12977
12978 /* end button procedures */
12979
12980 void
12981 PrintPosition(fp, move)
12982      FILE *fp;
12983      int move;
12984 {
12985     int i, j;
12986     
12987     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12988         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12989             char c = PieceToChar(boards[move][i][j]);
12990             fputc(c == 'x' ? '.' : c, fp);
12991             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12992         }
12993     }
12994     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12995       fprintf(fp, "white to play\n");
12996     else
12997       fprintf(fp, "black to play\n");
12998 }
12999
13000 void
13001 PrintOpponents(fp)
13002      FILE *fp;
13003 {
13004     if (gameInfo.white != NULL) {
13005         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13006     } else {
13007         fprintf(fp, "\n");
13008     }
13009 }
13010
13011 /* Find last component of program's own name, using some heuristics */
13012 void
13013 TidyProgramName(prog, host, buf)
13014      char *prog, *host, buf[MSG_SIZ];
13015 {
13016     char *p, *q;
13017     int local = (strcmp(host, "localhost") == 0);
13018     while (!local && (p = strchr(prog, ';')) != NULL) {
13019         p++;
13020         while (*p == ' ') p++;
13021         prog = p;
13022     }
13023     if (*prog == '"' || *prog == '\'') {
13024         q = strchr(prog + 1, *prog);
13025     } else {
13026         q = strchr(prog, ' ');
13027     }
13028     if (q == NULL) q = prog + strlen(prog);
13029     p = q;
13030     while (p >= prog && *p != '/' && *p != '\\') p--;
13031     p++;
13032     if(p == prog && *p == '"') p++;
13033     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13034     memcpy(buf, p, q - p);
13035     buf[q - p] = NULLCHAR;
13036     if (!local) {
13037         strcat(buf, "@");
13038         strcat(buf, host);
13039     }
13040 }
13041
13042 char *
13043 TimeControlTagValue()
13044 {
13045     char buf[MSG_SIZ];
13046     if (!appData.clockMode) {
13047         strcpy(buf, "-");
13048     } else if (movesPerSession > 0) {
13049         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
13050     } else if (timeIncrement == 0) {
13051         sprintf(buf, "%ld", timeControl/1000);
13052     } else {
13053         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13054     }
13055     return StrSave(buf);
13056 }
13057
13058 void
13059 SetGameInfo()
13060 {
13061     /* This routine is used only for certain modes */
13062     VariantClass v = gameInfo.variant;
13063     ChessMove r = GameUnfinished;
13064     char *p = NULL;
13065
13066     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13067         r = gameInfo.result; 
13068         p = gameInfo.resultDetails; 
13069         gameInfo.resultDetails = NULL;
13070     }
13071     ClearGameInfo(&gameInfo);
13072     gameInfo.variant = v;
13073
13074     switch (gameMode) {
13075       case MachinePlaysWhite:
13076         gameInfo.event = StrSave( appData.pgnEventHeader );
13077         gameInfo.site = StrSave(HostName());
13078         gameInfo.date = PGNDate();
13079         gameInfo.round = StrSave("-");
13080         gameInfo.white = StrSave(first.tidy);
13081         gameInfo.black = StrSave(UserName());
13082         gameInfo.timeControl = TimeControlTagValue();
13083         break;
13084
13085       case MachinePlaysBlack:
13086         gameInfo.event = StrSave( appData.pgnEventHeader );
13087         gameInfo.site = StrSave(HostName());
13088         gameInfo.date = PGNDate();
13089         gameInfo.round = StrSave("-");
13090         gameInfo.white = StrSave(UserName());
13091         gameInfo.black = StrSave(first.tidy);
13092         gameInfo.timeControl = TimeControlTagValue();
13093         break;
13094
13095       case TwoMachinesPlay:
13096         gameInfo.event = StrSave( appData.pgnEventHeader );
13097         gameInfo.site = StrSave(HostName());
13098         gameInfo.date = PGNDate();
13099         if (matchGame > 0) {
13100             char buf[MSG_SIZ];
13101             sprintf(buf, "%d", matchGame);
13102             gameInfo.round = StrSave(buf);
13103         } else {
13104             gameInfo.round = StrSave("-");
13105         }
13106         if (first.twoMachinesColor[0] == 'w') {
13107             gameInfo.white = StrSave(first.tidy);
13108             gameInfo.black = StrSave(second.tidy);
13109         } else {
13110             gameInfo.white = StrSave(second.tidy);
13111             gameInfo.black = StrSave(first.tidy);
13112         }
13113         gameInfo.timeControl = TimeControlTagValue();
13114         break;
13115
13116       case EditGame:
13117         gameInfo.event = StrSave("Edited game");
13118         gameInfo.site = StrSave(HostName());
13119         gameInfo.date = PGNDate();
13120         gameInfo.round = StrSave("-");
13121         gameInfo.white = StrSave("-");
13122         gameInfo.black = StrSave("-");
13123         gameInfo.result = r;
13124         gameInfo.resultDetails = p;
13125         break;
13126
13127       case EditPosition:
13128         gameInfo.event = StrSave("Edited position");
13129         gameInfo.site = StrSave(HostName());
13130         gameInfo.date = PGNDate();
13131         gameInfo.round = StrSave("-");
13132         gameInfo.white = StrSave("-");
13133         gameInfo.black = StrSave("-");
13134         break;
13135
13136       case IcsPlayingWhite:
13137       case IcsPlayingBlack:
13138       case IcsObserving:
13139       case IcsExamining:
13140         break;
13141
13142       case PlayFromGameFile:
13143         gameInfo.event = StrSave("Game from non-PGN file");
13144         gameInfo.site = StrSave(HostName());
13145         gameInfo.date = PGNDate();
13146         gameInfo.round = StrSave("-");
13147         gameInfo.white = StrSave("?");
13148         gameInfo.black = StrSave("?");
13149         break;
13150
13151       default:
13152         break;
13153     }
13154 }
13155
13156 void
13157 ReplaceComment(index, text)
13158      int index;
13159      char *text;
13160 {
13161     int len;
13162
13163     while (*text == '\n') text++;
13164     len = strlen(text);
13165     while (len > 0 && text[len - 1] == '\n') len--;
13166
13167     if (commentList[index] != NULL)
13168       free(commentList[index]);
13169
13170     if (len == 0) {
13171         commentList[index] = NULL;
13172         return;
13173     }
13174   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13175       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13176       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13177     commentList[index] = (char *) malloc(len + 2);
13178     strncpy(commentList[index], text, len);
13179     commentList[index][len] = '\n';
13180     commentList[index][len + 1] = NULLCHAR;
13181   } else { 
13182     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13183     char *p;
13184     commentList[index] = (char *) malloc(len + 6);
13185     strcpy(commentList[index], "{\n");
13186     strncpy(commentList[index]+2, text, len);
13187     commentList[index][len+2] = NULLCHAR;
13188     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13189     strcat(commentList[index], "\n}\n");
13190   }
13191 }
13192
13193 void
13194 CrushCRs(text)
13195      char *text;
13196 {
13197   char *p = text;
13198   char *q = text;
13199   char ch;
13200
13201   do {
13202     ch = *p++;
13203     if (ch == '\r') continue;
13204     *q++ = ch;
13205   } while (ch != '\0');
13206 }
13207
13208 void
13209 AppendComment(index, text, addBraces)
13210      int index;
13211      char *text;
13212      Boolean addBraces; // [HGM] braces: tells if we should add {}
13213 {
13214     int oldlen, len;
13215     char *old;
13216
13217 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13218     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13219
13220     CrushCRs(text);
13221     while (*text == '\n') text++;
13222     len = strlen(text);
13223     while (len > 0 && text[len - 1] == '\n') len--;
13224
13225     if (len == 0) return;
13226
13227     if (commentList[index] != NULL) {
13228         old = commentList[index];
13229         oldlen = strlen(old);
13230         while(commentList[index][oldlen-1] ==  '\n')
13231           commentList[index][--oldlen] = NULLCHAR;
13232         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13233         strcpy(commentList[index], old);
13234         free(old);
13235         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13236         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13237           if(addBraces) addBraces = FALSE; else { text++; len--; }
13238           while (*text == '\n') { text++; len--; }
13239           commentList[index][--oldlen] = NULLCHAR;
13240       }
13241         if(addBraces) strcat(commentList[index], "\n{\n");
13242         else          strcat(commentList[index], "\n");
13243         strcat(commentList[index], text);
13244         if(addBraces) strcat(commentList[index], "\n}\n");
13245         else          strcat(commentList[index], "\n");
13246     } else {
13247         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13248         if(addBraces)
13249              strcpy(commentList[index], "{\n");
13250         else commentList[index][0] = NULLCHAR;
13251         strcat(commentList[index], text);
13252         strcat(commentList[index], "\n");
13253         if(addBraces) strcat(commentList[index], "}\n");
13254     }
13255 }
13256
13257 static char * FindStr( char * text, char * sub_text )
13258 {
13259     char * result = strstr( text, sub_text );
13260
13261     if( result != NULL ) {
13262         result += strlen( sub_text );
13263     }
13264
13265     return result;
13266 }
13267
13268 /* [AS] Try to extract PV info from PGN comment */
13269 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13270 char *GetInfoFromComment( int index, char * text )
13271 {
13272     char * sep = text;
13273
13274     if( text != NULL && index > 0 ) {
13275         int score = 0;
13276         int depth = 0;
13277         int time = -1, sec = 0, deci;
13278         char * s_eval = FindStr( text, "[%eval " );
13279         char * s_emt = FindStr( text, "[%emt " );
13280
13281         if( s_eval != NULL || s_emt != NULL ) {
13282             /* New style */
13283             char delim;
13284
13285             if( s_eval != NULL ) {
13286                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13287                     return text;
13288                 }
13289
13290                 if( delim != ']' ) {
13291                     return text;
13292                 }
13293             }
13294
13295             if( s_emt != NULL ) {
13296             }
13297                 return text;
13298         }
13299         else {
13300             /* We expect something like: [+|-]nnn.nn/dd */
13301             int score_lo = 0;
13302
13303             if(*text != '{') return text; // [HGM] braces: must be normal comment
13304
13305             sep = strchr( text, '/' );
13306             if( sep == NULL || sep < (text+4) ) {
13307                 return text;
13308             }
13309
13310             time = -1; sec = -1; deci = -1;
13311             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13312                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13313                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13314                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13315                 return text;
13316             }
13317
13318             if( score_lo < 0 || score_lo >= 100 ) {
13319                 return text;
13320             }
13321
13322             if(sec >= 0) time = 600*time + 10*sec; else
13323             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13324
13325             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13326
13327             /* [HGM] PV time: now locate end of PV info */
13328             while( *++sep >= '0' && *sep <= '9'); // strip depth
13329             if(time >= 0)
13330             while( *++sep >= '0' && *sep <= '9'); // strip time
13331             if(sec >= 0)
13332             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13333             if(deci >= 0)
13334             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13335             while(*sep == ' ') sep++;
13336         }
13337
13338         if( depth <= 0 ) {
13339             return text;
13340         }
13341
13342         if( time < 0 ) {
13343             time = -1;
13344         }
13345
13346         pvInfoList[index-1].depth = depth;
13347         pvInfoList[index-1].score = score;
13348         pvInfoList[index-1].time  = 10*time; // centi-sec
13349         if(*sep == '}') *sep = 0; else *--sep = '{';
13350     }
13351     return sep;
13352 }
13353
13354 void
13355 SendToProgram(message, cps)
13356      char *message;
13357      ChessProgramState *cps;
13358 {
13359     int count, outCount, error;
13360     char buf[MSG_SIZ];
13361
13362     if (cps->pr == NULL) return;
13363     Attention(cps);
13364     
13365     if (appData.debugMode) {
13366         TimeMark now;
13367         GetTimeMark(&now);
13368         fprintf(debugFP, "%ld >%-6s: %s", 
13369                 SubtractTimeMarks(&now, &programStartTime),
13370                 cps->which, message);
13371     }
13372     
13373     count = strlen(message);
13374     outCount = OutputToProcess(cps->pr, message, count, &error);
13375     if (outCount < count && !exiting 
13376                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13377         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13378         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13379             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13380                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13381                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13382             } else {
13383                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13384             }
13385             gameInfo.resultDetails = StrSave(buf);
13386         }
13387         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13388     }
13389 }
13390
13391 void
13392 ReceiveFromProgram(isr, closure, message, count, error)
13393      InputSourceRef isr;
13394      VOIDSTAR closure;
13395      char *message;
13396      int count;
13397      int error;
13398 {
13399     char *end_str;
13400     char buf[MSG_SIZ];
13401     ChessProgramState *cps = (ChessProgramState *)closure;
13402
13403     if (isr != cps->isr) return; /* Killed intentionally */
13404     if (count <= 0) {
13405         if (count == 0) {
13406             sprintf(buf,
13407                     _("Error: %s chess program (%s) exited unexpectedly"),
13408                     cps->which, cps->program);
13409         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13410                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13411                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13412                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13413                 } else {
13414                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13415                 }
13416                 gameInfo.resultDetails = StrSave(buf);
13417             }
13418             RemoveInputSource(cps->isr);
13419             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13420         } else {
13421             sprintf(buf,
13422                     _("Error reading from %s chess program (%s)"),
13423                     cps->which, cps->program);
13424             RemoveInputSource(cps->isr);
13425
13426             /* [AS] Program is misbehaving badly... kill it */
13427             if( count == -2 ) {
13428                 DestroyChildProcess( cps->pr, 9 );
13429                 cps->pr = NoProc;
13430             }
13431
13432             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13433         }
13434         return;
13435     }
13436     
13437     if ((end_str = strchr(message, '\r')) != NULL)
13438       *end_str = NULLCHAR;
13439     if ((end_str = strchr(message, '\n')) != NULL)
13440       *end_str = NULLCHAR;
13441     
13442     if (appData.debugMode) {
13443         TimeMark now; int print = 1;
13444         char *quote = ""; char c; int i;
13445
13446         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13447                 char start = message[0];
13448                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13449                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13450                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13451                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13452                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13453                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13454                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13455                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13456                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13457                     print = (appData.engineComments >= 2);
13458                 }
13459                 message[0] = start; // restore original message
13460         }
13461         if(print) {
13462                 GetTimeMark(&now);
13463                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13464                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13465                         quote,
13466                         message);
13467         }
13468     }
13469
13470     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13471     if (appData.icsEngineAnalyze) {
13472         if (strstr(message, "whisper") != NULL ||
13473              strstr(message, "kibitz") != NULL || 
13474             strstr(message, "tellics") != NULL) return;
13475     }
13476
13477     HandleMachineMove(message, cps);
13478 }
13479
13480
13481 void
13482 SendTimeControl(cps, mps, tc, inc, sd, st)
13483      ChessProgramState *cps;
13484      int mps, inc, sd, st;
13485      long tc;
13486 {
13487     char buf[MSG_SIZ];
13488     int seconds;
13489
13490     if( timeControl_2 > 0 ) {
13491         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13492             tc = timeControl_2;
13493         }
13494     }
13495     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13496     inc /= cps->timeOdds;
13497     st  /= cps->timeOdds;
13498
13499     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13500
13501     if (st > 0) {
13502       /* Set exact time per move, normally using st command */
13503       if (cps->stKludge) {
13504         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13505         seconds = st % 60;
13506         if (seconds == 0) {
13507           sprintf(buf, "level 1 %d\n", st/60);
13508         } else {
13509           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13510         }
13511       } else {
13512         sprintf(buf, "st %d\n", st);
13513       }
13514     } else {
13515       /* Set conventional or incremental time control, using level command */
13516       if (seconds == 0) {
13517         /* Note old gnuchess bug -- minutes:seconds used to not work.
13518            Fixed in later versions, but still avoid :seconds
13519            when seconds is 0. */
13520         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13521       } else {
13522         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13523                 seconds, inc/1000);
13524       }
13525     }
13526     SendToProgram(buf, cps);
13527
13528     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13529     /* Orthogonally, limit search to given depth */
13530     if (sd > 0) {
13531       if (cps->sdKludge) {
13532         sprintf(buf, "depth\n%d\n", sd);
13533       } else {
13534         sprintf(buf, "sd %d\n", sd);
13535       }
13536       SendToProgram(buf, cps);
13537     }
13538
13539     if(cps->nps > 0) { /* [HGM] nps */
13540         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13541         else {
13542                 sprintf(buf, "nps %d\n", cps->nps);
13543               SendToProgram(buf, cps);
13544         }
13545     }
13546 }
13547
13548 ChessProgramState *WhitePlayer()
13549 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13550 {
13551     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13552        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13553         return &second;
13554     return &first;
13555 }
13556
13557 void
13558 SendTimeRemaining(cps, machineWhite)
13559      ChessProgramState *cps;
13560      int /*boolean*/ machineWhite;
13561 {
13562     char message[MSG_SIZ];
13563     long time, otime;
13564
13565     /* Note: this routine must be called when the clocks are stopped
13566        or when they have *just* been set or switched; otherwise
13567        it will be off by the time since the current tick started.
13568     */
13569     if (machineWhite) {
13570         time = whiteTimeRemaining / 10;
13571         otime = blackTimeRemaining / 10;
13572     } else {
13573         time = blackTimeRemaining / 10;
13574         otime = whiteTimeRemaining / 10;
13575     }
13576     /* [HGM] translate opponent's time by time-odds factor */
13577     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13578     if (appData.debugMode) {
13579         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13580     }
13581
13582     if (time <= 0) time = 1;
13583     if (otime <= 0) otime = 1;
13584     
13585     sprintf(message, "time %ld\n", time);
13586     SendToProgram(message, cps);
13587
13588     sprintf(message, "otim %ld\n", otime);
13589     SendToProgram(message, cps);
13590 }
13591
13592 int
13593 BoolFeature(p, name, loc, cps)
13594      char **p;
13595      char *name;
13596      int *loc;
13597      ChessProgramState *cps;
13598 {
13599   char buf[MSG_SIZ];
13600   int len = strlen(name);
13601   int val;
13602   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13603     (*p) += len + 1;
13604     sscanf(*p, "%d", &val);
13605     *loc = (val != 0);
13606     while (**p && **p != ' ') (*p)++;
13607     sprintf(buf, "accepted %s\n", name);
13608     SendToProgram(buf, cps);
13609     return TRUE;
13610   }
13611   return FALSE;
13612 }
13613
13614 int
13615 IntFeature(p, name, loc, cps)
13616      char **p;
13617      char *name;
13618      int *loc;
13619      ChessProgramState *cps;
13620 {
13621   char buf[MSG_SIZ];
13622   int len = strlen(name);
13623   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13624     (*p) += len + 1;
13625     sscanf(*p, "%d", loc);
13626     while (**p && **p != ' ') (*p)++;
13627     sprintf(buf, "accepted %s\n", name);
13628     SendToProgram(buf, cps);
13629     return TRUE;
13630   }
13631   return FALSE;
13632 }
13633
13634 int
13635 StringFeature(p, name, loc, cps)
13636      char **p;
13637      char *name;
13638      char loc[];
13639      ChessProgramState *cps;
13640 {
13641   char buf[MSG_SIZ];
13642   int len = strlen(name);
13643   if (strncmp((*p), name, len) == 0
13644       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13645     (*p) += len + 2;
13646     sscanf(*p, "%[^\"]", loc);
13647     while (**p && **p != '\"') (*p)++;
13648     if (**p == '\"') (*p)++;
13649     sprintf(buf, "accepted %s\n", name);
13650     SendToProgram(buf, cps);
13651     return TRUE;
13652   }
13653   return FALSE;
13654 }
13655
13656 int 
13657 ParseOption(Option *opt, ChessProgramState *cps)
13658 // [HGM] options: process the string that defines an engine option, and determine
13659 // name, type, default value, and allowed value range
13660 {
13661         char *p, *q, buf[MSG_SIZ];
13662         int n, min = (-1)<<31, max = 1<<31, def;
13663
13664         if(p = strstr(opt->name, " -spin ")) {
13665             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13666             if(max < min) max = min; // enforce consistency
13667             if(def < min) def = min;
13668             if(def > max) def = max;
13669             opt->value = def;
13670             opt->min = min;
13671             opt->max = max;
13672             opt->type = Spin;
13673         } else if((p = strstr(opt->name, " -slider "))) {
13674             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13675             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13676             if(max < min) max = min; // enforce consistency
13677             if(def < min) def = min;
13678             if(def > max) def = max;
13679             opt->value = def;
13680             opt->min = min;
13681             opt->max = max;
13682             opt->type = Spin; // Slider;
13683         } else if((p = strstr(opt->name, " -string "))) {
13684             opt->textValue = p+9;
13685             opt->type = TextBox;
13686         } else if((p = strstr(opt->name, " -file "))) {
13687             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13688             opt->textValue = p+7;
13689             opt->type = TextBox; // FileName;
13690         } else if((p = strstr(opt->name, " -path "))) {
13691             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13692             opt->textValue = p+7;
13693             opt->type = TextBox; // PathName;
13694         } else if(p = strstr(opt->name, " -check ")) {
13695             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13696             opt->value = (def != 0);
13697             opt->type = CheckBox;
13698         } else if(p = strstr(opt->name, " -combo ")) {
13699             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13700             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13701             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13702             opt->value = n = 0;
13703             while(q = StrStr(q, " /// ")) {
13704                 n++; *q = 0;    // count choices, and null-terminate each of them
13705                 q += 5;
13706                 if(*q == '*') { // remember default, which is marked with * prefix
13707                     q++;
13708                     opt->value = n;
13709                 }
13710                 cps->comboList[cps->comboCnt++] = q;
13711             }
13712             cps->comboList[cps->comboCnt++] = NULL;
13713             opt->max = n + 1;
13714             opt->type = ComboBox;
13715         } else if(p = strstr(opt->name, " -button")) {
13716             opt->type = Button;
13717         } else if(p = strstr(opt->name, " -save")) {
13718             opt->type = SaveButton;
13719         } else return FALSE;
13720         *p = 0; // terminate option name
13721         // now look if the command-line options define a setting for this engine option.
13722         if(cps->optionSettings && cps->optionSettings[0])
13723             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13724         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13725                 sprintf(buf, "option %s", p);
13726                 if(p = strstr(buf, ",")) *p = 0;
13727                 strcat(buf, "\n");
13728                 SendToProgram(buf, cps);
13729         }
13730         return TRUE;
13731 }
13732
13733 void
13734 FeatureDone(cps, val)
13735      ChessProgramState* cps;
13736      int val;
13737 {
13738   DelayedEventCallback cb = GetDelayedEvent();
13739   if ((cb == InitBackEnd3 && cps == &first) ||
13740       (cb == TwoMachinesEventIfReady && cps == &second)) {
13741     CancelDelayedEvent();
13742     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13743   }
13744   cps->initDone = val;
13745 }
13746
13747 /* Parse feature command from engine */
13748 void
13749 ParseFeatures(args, cps)
13750      char* args;
13751      ChessProgramState *cps;  
13752 {
13753   char *p = args;
13754   char *q;
13755   int val;
13756   char buf[MSG_SIZ];
13757
13758   for (;;) {
13759     while (*p == ' ') p++;
13760     if (*p == NULLCHAR) return;
13761
13762     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13763     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13764     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13765     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13766     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13767     if (BoolFeature(&p, "reuse", &val, cps)) {
13768       /* Engine can disable reuse, but can't enable it if user said no */
13769       if (!val) cps->reuse = FALSE;
13770       continue;
13771     }
13772     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13773     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13774       if (gameMode == TwoMachinesPlay) {
13775         DisplayTwoMachinesTitle();
13776       } else {
13777         DisplayTitle("");
13778       }
13779       continue;
13780     }
13781     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13782     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13783     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13784     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13785     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13786     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13787     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13788     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13789     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13790     if (IntFeature(&p, "done", &val, cps)) {
13791       FeatureDone(cps, val);
13792       continue;
13793     }
13794     /* Added by Tord: */
13795     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13796     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13797     /* End of additions by Tord */
13798
13799     /* [HGM] added features: */
13800     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13801     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13802     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13803     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13804     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13805     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13806     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13807         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13808             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13809             SendToProgram(buf, cps);
13810             continue;
13811         }
13812         if(cps->nrOptions >= MAX_OPTIONS) {
13813             cps->nrOptions--;
13814             sprintf(buf, "%s engine has too many options\n", cps->which);
13815             DisplayError(buf, 0);
13816         }
13817         continue;
13818     }
13819     /* End of additions by HGM */
13820
13821     /* unknown feature: complain and skip */
13822     q = p;
13823     while (*q && *q != '=') q++;
13824     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13825     SendToProgram(buf, cps);
13826     p = q;
13827     if (*p == '=') {
13828       p++;
13829       if (*p == '\"') {
13830         p++;
13831         while (*p && *p != '\"') p++;
13832         if (*p == '\"') p++;
13833       } else {
13834         while (*p && *p != ' ') p++;
13835       }
13836     }
13837   }
13838
13839 }
13840
13841 void
13842 PeriodicUpdatesEvent(newState)
13843      int newState;
13844 {
13845     if (newState == appData.periodicUpdates)
13846       return;
13847
13848     appData.periodicUpdates=newState;
13849
13850     /* Display type changes, so update it now */
13851 //    DisplayAnalysis();
13852
13853     /* Get the ball rolling again... */
13854     if (newState) {
13855         AnalysisPeriodicEvent(1);
13856         StartAnalysisClock();
13857     }
13858 }
13859
13860 void
13861 PonderNextMoveEvent(newState)
13862      int newState;
13863 {
13864     if (newState == appData.ponderNextMove) return;
13865     if (gameMode == EditPosition) EditPositionDone(TRUE);
13866     if (newState) {
13867         SendToProgram("hard\n", &first);
13868         if (gameMode == TwoMachinesPlay) {
13869             SendToProgram("hard\n", &second);
13870         }
13871     } else {
13872         SendToProgram("easy\n", &first);
13873         thinkOutput[0] = NULLCHAR;
13874         if (gameMode == TwoMachinesPlay) {
13875             SendToProgram("easy\n", &second);
13876         }
13877     }
13878     appData.ponderNextMove = newState;
13879 }
13880
13881 void
13882 NewSettingEvent(option, feature, command, value)
13883      char *command;
13884      int option, value, *feature;
13885 {
13886     char buf[MSG_SIZ];
13887
13888     if (gameMode == EditPosition) EditPositionDone(TRUE);
13889     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13890     if(feature == NULL || *feature) SendToProgram(buf, &first);
13891     if (gameMode == TwoMachinesPlay) {
13892         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
13893     }
13894 }
13895
13896 void
13897 ShowThinkingEvent()
13898 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13899 {
13900     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13901     int newState = appData.showThinking
13902         // [HGM] thinking: other features now need thinking output as well
13903         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13904     
13905     if (oldState == newState) return;
13906     oldState = newState;
13907     if (gameMode == EditPosition) EditPositionDone(TRUE);
13908     if (oldState) {
13909         SendToProgram("post\n", &first);
13910         if (gameMode == TwoMachinesPlay) {
13911             SendToProgram("post\n", &second);
13912         }
13913     } else {
13914         SendToProgram("nopost\n", &first);
13915         thinkOutput[0] = NULLCHAR;
13916         if (gameMode == TwoMachinesPlay) {
13917             SendToProgram("nopost\n", &second);
13918         }
13919     }
13920 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13921 }
13922
13923 void
13924 AskQuestionEvent(title, question, replyPrefix, which)
13925      char *title; char *question; char *replyPrefix; char *which;
13926 {
13927   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13928   if (pr == NoProc) return;
13929   AskQuestion(title, question, replyPrefix, pr);
13930 }
13931
13932 void
13933 DisplayMove(moveNumber)
13934      int moveNumber;
13935 {
13936     char message[MSG_SIZ];
13937     char res[MSG_SIZ];
13938     char cpThinkOutput[MSG_SIZ];
13939
13940     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13941     
13942     if (moveNumber == forwardMostMove - 1 || 
13943         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13944
13945         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13946
13947         if (strchr(cpThinkOutput, '\n')) {
13948             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13949         }
13950     } else {
13951         *cpThinkOutput = NULLCHAR;
13952     }
13953
13954     /* [AS] Hide thinking from human user */
13955     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13956         *cpThinkOutput = NULLCHAR;
13957         if( thinkOutput[0] != NULLCHAR ) {
13958             int i;
13959
13960             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13961                 cpThinkOutput[i] = '.';
13962             }
13963             cpThinkOutput[i] = NULLCHAR;
13964             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13965         }
13966     }
13967
13968     if (moveNumber == forwardMostMove - 1 &&
13969         gameInfo.resultDetails != NULL) {
13970         if (gameInfo.resultDetails[0] == NULLCHAR) {
13971             sprintf(res, " %s", PGNResult(gameInfo.result));
13972         } else {
13973             sprintf(res, " {%s} %s",
13974                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13975         }
13976     } else {
13977         res[0] = NULLCHAR;
13978     }
13979
13980     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13981         DisplayMessage(res, cpThinkOutput);
13982     } else {
13983         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13984                 WhiteOnMove(moveNumber) ? " " : ".. ",
13985                 parseList[moveNumber], res);
13986         DisplayMessage(message, cpThinkOutput);
13987     }
13988 }
13989
13990 void
13991 DisplayComment(moveNumber, text)
13992      int moveNumber;
13993      char *text;
13994 {
13995     char title[MSG_SIZ];
13996     char buf[8000]; // comment can be long!
13997     int score, depth;
13998     
13999     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14000       strcpy(title, "Comment");
14001     } else {
14002       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
14003               WhiteOnMove(moveNumber) ? " " : ".. ",
14004               parseList[moveNumber]);
14005     }
14006     // [HGM] PV info: display PV info together with (or as) comment
14007     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14008       if(text == NULL) text = "";                                           
14009       score = pvInfoList[moveNumber].score;
14010       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14011               depth, (pvInfoList[moveNumber].time+50)/100, text);
14012       text = buf;
14013     }
14014     if (text != NULL && (appData.autoDisplayComment || commentUp))
14015         CommentPopUp(title, text);
14016 }
14017
14018 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14019  * might be busy thinking or pondering.  It can be omitted if your
14020  * gnuchess is configured to stop thinking immediately on any user
14021  * input.  However, that gnuchess feature depends on the FIONREAD
14022  * ioctl, which does not work properly on some flavors of Unix.
14023  */
14024 void
14025 Attention(cps)
14026      ChessProgramState *cps;
14027 {
14028 #if ATTENTION
14029     if (!cps->useSigint) return;
14030     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14031     switch (gameMode) {
14032       case MachinePlaysWhite:
14033       case MachinePlaysBlack:
14034       case TwoMachinesPlay:
14035       case IcsPlayingWhite:
14036       case IcsPlayingBlack:
14037       case AnalyzeMode:
14038       case AnalyzeFile:
14039         /* Skip if we know it isn't thinking */
14040         if (!cps->maybeThinking) return;
14041         if (appData.debugMode)
14042           fprintf(debugFP, "Interrupting %s\n", cps->which);
14043         InterruptChildProcess(cps->pr);
14044         cps->maybeThinking = FALSE;
14045         break;
14046       default:
14047         break;
14048     }
14049 #endif /*ATTENTION*/
14050 }
14051
14052 int
14053 CheckFlags()
14054 {
14055     if (whiteTimeRemaining <= 0) {
14056         if (!whiteFlag) {
14057             whiteFlag = TRUE;
14058             if (appData.icsActive) {
14059                 if (appData.autoCallFlag &&
14060                     gameMode == IcsPlayingBlack && !blackFlag) {
14061                   SendToICS(ics_prefix);
14062                   SendToICS("flag\n");
14063                 }
14064             } else {
14065                 if (blackFlag) {
14066                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14067                 } else {
14068                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14069                     if (appData.autoCallFlag) {
14070                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14071                         return TRUE;
14072                     }
14073                 }
14074             }
14075         }
14076     }
14077     if (blackTimeRemaining <= 0) {
14078         if (!blackFlag) {
14079             blackFlag = TRUE;
14080             if (appData.icsActive) {
14081                 if (appData.autoCallFlag &&
14082                     gameMode == IcsPlayingWhite && !whiteFlag) {
14083                   SendToICS(ics_prefix);
14084                   SendToICS("flag\n");
14085                 }
14086             } else {
14087                 if (whiteFlag) {
14088                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14089                 } else {
14090                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14091                     if (appData.autoCallFlag) {
14092                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14093                         return TRUE;
14094                     }
14095                 }
14096             }
14097         }
14098     }
14099     return FALSE;
14100 }
14101
14102 void
14103 CheckTimeControl()
14104 {
14105     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14106         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14107
14108     /*
14109      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14110      */
14111     if ( !WhiteOnMove(forwardMostMove) )
14112         /* White made time control */
14113         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14114         /* [HGM] time odds: correct new time quota for time odds! */
14115                                             / WhitePlayer()->timeOdds;
14116       else
14117         /* Black made time control */
14118         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14119                                             / WhitePlayer()->other->timeOdds;
14120 }
14121
14122 void
14123 DisplayBothClocks()
14124 {
14125     int wom = gameMode == EditPosition ?
14126       !blackPlaysFirst : WhiteOnMove(currentMove);
14127     DisplayWhiteClock(whiteTimeRemaining, wom);
14128     DisplayBlackClock(blackTimeRemaining, !wom);
14129 }
14130
14131
14132 /* Timekeeping seems to be a portability nightmare.  I think everyone
14133    has ftime(), but I'm really not sure, so I'm including some ifdefs
14134    to use other calls if you don't.  Clocks will be less accurate if
14135    you have neither ftime nor gettimeofday.
14136 */
14137
14138 /* VS 2008 requires the #include outside of the function */
14139 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14140 #include <sys/timeb.h>
14141 #endif
14142
14143 /* Get the current time as a TimeMark */
14144 void
14145 GetTimeMark(tm)
14146      TimeMark *tm;
14147 {
14148 #if HAVE_GETTIMEOFDAY
14149
14150     struct timeval timeVal;
14151     struct timezone timeZone;
14152
14153     gettimeofday(&timeVal, &timeZone);
14154     tm->sec = (long) timeVal.tv_sec; 
14155     tm->ms = (int) (timeVal.tv_usec / 1000L);
14156
14157 #else /*!HAVE_GETTIMEOFDAY*/
14158 #if HAVE_FTIME
14159
14160 // include <sys/timeb.h> / moved to just above start of function
14161     struct timeb timeB;
14162
14163     ftime(&timeB);
14164     tm->sec = (long) timeB.time;
14165     tm->ms = (int) timeB.millitm;
14166
14167 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14168     tm->sec = (long) time(NULL);
14169     tm->ms = 0;
14170 #endif
14171 #endif
14172 }
14173
14174 /* Return the difference in milliseconds between two
14175    time marks.  We assume the difference will fit in a long!
14176 */
14177 long
14178 SubtractTimeMarks(tm2, tm1)
14179      TimeMark *tm2, *tm1;
14180 {
14181     return 1000L*(tm2->sec - tm1->sec) +
14182            (long) (tm2->ms - tm1->ms);
14183 }
14184
14185
14186 /*
14187  * Code to manage the game clocks.
14188  *
14189  * In tournament play, black starts the clock and then white makes a move.
14190  * We give the human user a slight advantage if he is playing white---the
14191  * clocks don't run until he makes his first move, so it takes zero time.
14192  * Also, we don't account for network lag, so we could get out of sync
14193  * with GNU Chess's clock -- but then, referees are always right.  
14194  */
14195
14196 static TimeMark tickStartTM;
14197 static long intendedTickLength;
14198
14199 long
14200 NextTickLength(timeRemaining)
14201      long timeRemaining;
14202 {
14203     long nominalTickLength, nextTickLength;
14204
14205     if (timeRemaining > 0L && timeRemaining <= 10000L)
14206       nominalTickLength = 100L;
14207     else
14208       nominalTickLength = 1000L;
14209     nextTickLength = timeRemaining % nominalTickLength;
14210     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14211
14212     return nextTickLength;
14213 }
14214
14215 /* Adjust clock one minute up or down */
14216 void
14217 AdjustClock(Boolean which, int dir)
14218 {
14219     if(which) blackTimeRemaining += 60000*dir;
14220     else      whiteTimeRemaining += 60000*dir;
14221     DisplayBothClocks();
14222 }
14223
14224 /* Stop clocks and reset to a fresh time control */
14225 void
14226 ResetClocks() 
14227 {
14228     (void) StopClockTimer();
14229     if (appData.icsActive) {
14230         whiteTimeRemaining = blackTimeRemaining = 0;
14231     } else if (searchTime) {
14232         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14233         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14234     } else { /* [HGM] correct new time quote for time odds */
14235         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14236         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14237     }
14238     if (whiteFlag || blackFlag) {
14239         DisplayTitle("");
14240         whiteFlag = blackFlag = FALSE;
14241     }
14242     DisplayBothClocks();
14243 }
14244
14245 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14246
14247 /* Decrement running clock by amount of time that has passed */
14248 void
14249 DecrementClocks()
14250 {
14251     long timeRemaining;
14252     long lastTickLength, fudge;
14253     TimeMark now;
14254
14255     if (!appData.clockMode) return;
14256     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14257         
14258     GetTimeMark(&now);
14259
14260     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14261
14262     /* Fudge if we woke up a little too soon */
14263     fudge = intendedTickLength - lastTickLength;
14264     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14265
14266     if (WhiteOnMove(forwardMostMove)) {
14267         if(whiteNPS >= 0) lastTickLength = 0;
14268         timeRemaining = whiteTimeRemaining -= lastTickLength;
14269         DisplayWhiteClock(whiteTimeRemaining - fudge,
14270                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14271     } else {
14272         if(blackNPS >= 0) lastTickLength = 0;
14273         timeRemaining = blackTimeRemaining -= lastTickLength;
14274         DisplayBlackClock(blackTimeRemaining - fudge,
14275                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14276     }
14277
14278     if (CheckFlags()) return;
14279         
14280     tickStartTM = now;
14281     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14282     StartClockTimer(intendedTickLength);
14283
14284     /* if the time remaining has fallen below the alarm threshold, sound the
14285      * alarm. if the alarm has sounded and (due to a takeback or time control
14286      * with increment) the time remaining has increased to a level above the
14287      * threshold, reset the alarm so it can sound again. 
14288      */
14289     
14290     if (appData.icsActive && appData.icsAlarm) {
14291
14292         /* make sure we are dealing with the user's clock */
14293         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14294                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14295            )) return;
14296
14297         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14298             alarmSounded = FALSE;
14299         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
14300             PlayAlarmSound();
14301             alarmSounded = TRUE;
14302         }
14303     }
14304 }
14305
14306
14307 /* A player has just moved, so stop the previously running
14308    clock and (if in clock mode) start the other one.
14309    We redisplay both clocks in case we're in ICS mode, because
14310    ICS gives us an update to both clocks after every move.
14311    Note that this routine is called *after* forwardMostMove
14312    is updated, so the last fractional tick must be subtracted
14313    from the color that is *not* on move now.
14314 */
14315 void
14316 SwitchClocks(int newMoveNr)
14317 {
14318     long lastTickLength;
14319     TimeMark now;
14320     int flagged = FALSE;
14321
14322     GetTimeMark(&now);
14323
14324     if (StopClockTimer() && appData.clockMode) {
14325         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14326         if (!WhiteOnMove(forwardMostMove)) {
14327             if(blackNPS >= 0) lastTickLength = 0;
14328             blackTimeRemaining -= lastTickLength;
14329            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14330 //         if(pvInfoList[forwardMostMove-1].time == -1)
14331                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14332                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14333         } else {
14334            if(whiteNPS >= 0) lastTickLength = 0;
14335            whiteTimeRemaining -= lastTickLength;
14336            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14337 //         if(pvInfoList[forwardMostMove-1].time == -1)
14338                  pvInfoList[forwardMostMove-1].time = 
14339                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14340         }
14341         flagged = CheckFlags();
14342     }
14343     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14344     CheckTimeControl();
14345
14346     if (flagged || !appData.clockMode) return;
14347
14348     switch (gameMode) {
14349       case MachinePlaysBlack:
14350       case MachinePlaysWhite:
14351       case BeginningOfGame:
14352         if (pausing) return;
14353         break;
14354
14355       case EditGame:
14356       case PlayFromGameFile:
14357       case IcsExamining:
14358         return;
14359
14360       default:
14361         break;
14362     }
14363
14364     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14365         if(WhiteOnMove(forwardMostMove))
14366              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14367         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14368     }
14369
14370     tickStartTM = now;
14371     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14372       whiteTimeRemaining : blackTimeRemaining);
14373     StartClockTimer(intendedTickLength);
14374 }
14375         
14376
14377 /* Stop both clocks */
14378 void
14379 StopClocks()
14380 {       
14381     long lastTickLength;
14382     TimeMark now;
14383
14384     if (!StopClockTimer()) return;
14385     if (!appData.clockMode) return;
14386
14387     GetTimeMark(&now);
14388
14389     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14390     if (WhiteOnMove(forwardMostMove)) {
14391         if(whiteNPS >= 0) lastTickLength = 0;
14392         whiteTimeRemaining -= lastTickLength;
14393         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14394     } else {
14395         if(blackNPS >= 0) lastTickLength = 0;
14396         blackTimeRemaining -= lastTickLength;
14397         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14398     }
14399     CheckFlags();
14400 }
14401         
14402 /* Start clock of player on move.  Time may have been reset, so
14403    if clock is already running, stop and restart it. */
14404 void
14405 StartClocks()
14406 {
14407     (void) StopClockTimer(); /* in case it was running already */
14408     DisplayBothClocks();
14409     if (CheckFlags()) return;
14410
14411     if (!appData.clockMode) return;
14412     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14413
14414     GetTimeMark(&tickStartTM);
14415     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14416       whiteTimeRemaining : blackTimeRemaining);
14417
14418    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14419     whiteNPS = blackNPS = -1; 
14420     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14421        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14422         whiteNPS = first.nps;
14423     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14424        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14425         blackNPS = first.nps;
14426     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14427         whiteNPS = second.nps;
14428     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14429         blackNPS = second.nps;
14430     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14431
14432     StartClockTimer(intendedTickLength);
14433 }
14434
14435 char *
14436 TimeString(ms)
14437      long ms;
14438 {
14439     long second, minute, hour, day;
14440     char *sign = "";
14441     static char buf[32];
14442     
14443     if (ms > 0 && ms <= 9900) {
14444       /* convert milliseconds to tenths, rounding up */
14445       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14446
14447       sprintf(buf, " %03.1f ", tenths/10.0);
14448       return buf;
14449     }
14450
14451     /* convert milliseconds to seconds, rounding up */
14452     /* use floating point to avoid strangeness of integer division
14453        with negative dividends on many machines */
14454     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14455
14456     if (second < 0) {
14457         sign = "-";
14458         second = -second;
14459     }
14460     
14461     day = second / (60 * 60 * 24);
14462     second = second % (60 * 60 * 24);
14463     hour = second / (60 * 60);
14464     second = second % (60 * 60);
14465     minute = second / 60;
14466     second = second % 60;
14467     
14468     if (day > 0)
14469       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14470               sign, day, hour, minute, second);
14471     else if (hour > 0)
14472       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14473     else
14474       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14475     
14476     return buf;
14477 }
14478
14479
14480 /*
14481  * This is necessary because some C libraries aren't ANSI C compliant yet.
14482  */
14483 char *
14484 StrStr(string, match)
14485      char *string, *match;
14486 {
14487     int i, length;
14488     
14489     length = strlen(match);
14490     
14491     for (i = strlen(string) - length; i >= 0; i--, string++)
14492       if (!strncmp(match, string, length))
14493         return string;
14494     
14495     return NULL;
14496 }
14497
14498 char *
14499 StrCaseStr(string, match)
14500      char *string, *match;
14501 {
14502     int i, j, length;
14503     
14504     length = strlen(match);
14505     
14506     for (i = strlen(string) - length; i >= 0; i--, string++) {
14507         for (j = 0; j < length; j++) {
14508             if (ToLower(match[j]) != ToLower(string[j]))
14509               break;
14510         }
14511         if (j == length) return string;
14512     }
14513
14514     return NULL;
14515 }
14516
14517 #ifndef _amigados
14518 int
14519 StrCaseCmp(s1, s2)
14520      char *s1, *s2;
14521 {
14522     char c1, c2;
14523     
14524     for (;;) {
14525         c1 = ToLower(*s1++);
14526         c2 = ToLower(*s2++);
14527         if (c1 > c2) return 1;
14528         if (c1 < c2) return -1;
14529         if (c1 == NULLCHAR) return 0;
14530     }
14531 }
14532
14533
14534 int
14535 ToLower(c)
14536      int c;
14537 {
14538     return isupper(c) ? tolower(c) : c;
14539 }
14540
14541
14542 int
14543 ToUpper(c)
14544      int c;
14545 {
14546     return islower(c) ? toupper(c) : c;
14547 }
14548 #endif /* !_amigados    */
14549
14550 char *
14551 StrSave(s)
14552      char *s;
14553 {
14554     char *ret;
14555
14556     if ((ret = (char *) malloc(strlen(s) + 1))) {
14557         strcpy(ret, s);
14558     }
14559     return ret;
14560 }
14561
14562 char *
14563 StrSavePtr(s, savePtr)
14564      char *s, **savePtr;
14565 {
14566     if (*savePtr) {
14567         free(*savePtr);
14568     }
14569     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14570         strcpy(*savePtr, s);
14571     }
14572     return(*savePtr);
14573 }
14574
14575 char *
14576 PGNDate()
14577 {
14578     time_t clock;
14579     struct tm *tm;
14580     char buf[MSG_SIZ];
14581
14582     clock = time((time_t *)NULL);
14583     tm = localtime(&clock);
14584     sprintf(buf, "%04d.%02d.%02d",
14585             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14586     return StrSave(buf);
14587 }
14588
14589
14590 char *
14591 PositionToFEN(move, overrideCastling)
14592      int move;
14593      char *overrideCastling;
14594 {
14595     int i, j, fromX, fromY, toX, toY;
14596     int whiteToPlay;
14597     char buf[128];
14598     char *p, *q;
14599     int emptycount;
14600     ChessSquare piece;
14601
14602     whiteToPlay = (gameMode == EditPosition) ?
14603       !blackPlaysFirst : (move % 2 == 0);
14604     p = buf;
14605
14606     /* Piece placement data */
14607     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14608         emptycount = 0;
14609         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14610             if (boards[move][i][j] == EmptySquare) {
14611                 emptycount++;
14612             } else { ChessSquare piece = boards[move][i][j];
14613                 if (emptycount > 0) {
14614                     if(emptycount<10) /* [HGM] can be >= 10 */
14615                         *p++ = '0' + emptycount;
14616                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14617                     emptycount = 0;
14618                 }
14619                 if(PieceToChar(piece) == '+') {
14620                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14621                     *p++ = '+';
14622                     piece = (ChessSquare)(DEMOTED piece);
14623                 } 
14624                 *p++ = PieceToChar(piece);
14625                 if(p[-1] == '~') {
14626                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14627                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14628                     *p++ = '~';
14629                 }
14630             }
14631         }
14632         if (emptycount > 0) {
14633             if(emptycount<10) /* [HGM] can be >= 10 */
14634                 *p++ = '0' + emptycount;
14635             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14636             emptycount = 0;
14637         }
14638         *p++ = '/';
14639     }
14640     *(p - 1) = ' ';
14641
14642     /* [HGM] print Crazyhouse or Shogi holdings */
14643     if( gameInfo.holdingsWidth ) {
14644         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14645         q = p;
14646         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14647             piece = boards[move][i][BOARD_WIDTH-1];
14648             if( piece != EmptySquare )
14649               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14650                   *p++ = PieceToChar(piece);
14651         }
14652         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14653             piece = boards[move][BOARD_HEIGHT-i-1][0];
14654             if( piece != EmptySquare )
14655               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14656                   *p++ = PieceToChar(piece);
14657         }
14658
14659         if( q == p ) *p++ = '-';
14660         *p++ = ']';
14661         *p++ = ' ';
14662     }
14663
14664     /* Active color */
14665     *p++ = whiteToPlay ? 'w' : 'b';
14666     *p++ = ' ';
14667
14668   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14669     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14670   } else {
14671   if(nrCastlingRights) {
14672      q = p;
14673      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14674        /* [HGM] write directly from rights */
14675            if(boards[move][CASTLING][2] != NoRights &&
14676               boards[move][CASTLING][0] != NoRights   )
14677                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14678            if(boards[move][CASTLING][2] != NoRights &&
14679               boards[move][CASTLING][1] != NoRights   )
14680                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14681            if(boards[move][CASTLING][5] != NoRights &&
14682               boards[move][CASTLING][3] != NoRights   )
14683                 *p++ = boards[move][CASTLING][3] + AAA;
14684            if(boards[move][CASTLING][5] != NoRights &&
14685               boards[move][CASTLING][4] != NoRights   )
14686                 *p++ = boards[move][CASTLING][4] + AAA;
14687      } else {
14688
14689         /* [HGM] write true castling rights */
14690         if( nrCastlingRights == 6 ) {
14691             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14692                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14693             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14694                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14695             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14696                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14697             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14698                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14699         }
14700      }
14701      if (q == p) *p++ = '-'; /* No castling rights */
14702      *p++ = ' ';
14703   }
14704
14705   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14706      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14707     /* En passant target square */
14708     if (move > backwardMostMove) {
14709         fromX = moveList[move - 1][0] - AAA;
14710         fromY = moveList[move - 1][1] - ONE;
14711         toX = moveList[move - 1][2] - AAA;
14712         toY = moveList[move - 1][3] - ONE;
14713         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14714             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14715             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14716             fromX == toX) {
14717             /* 2-square pawn move just happened */
14718             *p++ = toX + AAA;
14719             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14720         } else {
14721             *p++ = '-';
14722         }
14723     } else if(move == backwardMostMove) {
14724         // [HGM] perhaps we should always do it like this, and forget the above?
14725         if((signed char)boards[move][EP_STATUS] >= 0) {
14726             *p++ = boards[move][EP_STATUS] + AAA;
14727             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14728         } else {
14729             *p++ = '-';
14730         }
14731     } else {
14732         *p++ = '-';
14733     }
14734     *p++ = ' ';
14735   }
14736   }
14737
14738     /* [HGM] find reversible plies */
14739     {   int i = 0, j=move;
14740
14741         if (appData.debugMode) { int k;
14742             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14743             for(k=backwardMostMove; k<=forwardMostMove; k++)
14744                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14745
14746         }
14747
14748         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14749         if( j == backwardMostMove ) i += initialRulePlies;
14750         sprintf(p, "%d ", i);
14751         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14752     }
14753     /* Fullmove number */
14754     sprintf(p, "%d", (move / 2) + 1);
14755     
14756     return StrSave(buf);
14757 }
14758
14759 Boolean
14760 ParseFEN(board, blackPlaysFirst, fen)
14761     Board board;
14762      int *blackPlaysFirst;
14763      char *fen;
14764 {
14765     int i, j;
14766     char *p;
14767     int emptycount;
14768     ChessSquare piece;
14769
14770     p = fen;
14771
14772     /* [HGM] by default clear Crazyhouse holdings, if present */
14773     if(gameInfo.holdingsWidth) {
14774        for(i=0; i<BOARD_HEIGHT; i++) {
14775            board[i][0]             = EmptySquare; /* black holdings */
14776            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14777            board[i][1]             = (ChessSquare) 0; /* black counts */
14778            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14779        }
14780     }
14781
14782     /* Piece placement data */
14783     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14784         j = 0;
14785         for (;;) {
14786             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14787                 if (*p == '/') p++;
14788                 emptycount = gameInfo.boardWidth - j;
14789                 while (emptycount--)
14790                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14791                 break;
14792 #if(BOARD_FILES >= 10)
14793             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14794                 p++; emptycount=10;
14795                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14796                 while (emptycount--)
14797                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14798 #endif
14799             } else if (isdigit(*p)) {
14800                 emptycount = *p++ - '0';
14801                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14802                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14803                 while (emptycount--)
14804                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14805             } else if (*p == '+' || isalpha(*p)) {
14806                 if (j >= gameInfo.boardWidth) return FALSE;
14807                 if(*p=='+') {
14808                     piece = CharToPiece(*++p);
14809                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14810                     piece = (ChessSquare) (PROMOTED piece ); p++;
14811                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14812                 } else piece = CharToPiece(*p++);
14813
14814                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14815                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14816                     piece = (ChessSquare) (PROMOTED piece);
14817                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14818                     p++;
14819                 }
14820                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14821             } else {
14822                 return FALSE;
14823             }
14824         }
14825     }
14826     while (*p == '/' || *p == ' ') p++;
14827
14828     /* [HGM] look for Crazyhouse holdings here */
14829     while(*p==' ') p++;
14830     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14831         if(*p == '[') p++;
14832         if(*p == '-' ) *p++; /* empty holdings */ else {
14833             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14834             /* if we would allow FEN reading to set board size, we would   */
14835             /* have to add holdings and shift the board read so far here   */
14836             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14837                 *p++;
14838                 if((int) piece >= (int) BlackPawn ) {
14839                     i = (int)piece - (int)BlackPawn;
14840                     i = PieceToNumber((ChessSquare)i);
14841                     if( i >= gameInfo.holdingsSize ) return FALSE;
14842                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14843                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14844                 } else {
14845                     i = (int)piece - (int)WhitePawn;
14846                     i = PieceToNumber((ChessSquare)i);
14847                     if( i >= gameInfo.holdingsSize ) return FALSE;
14848                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14849                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14850                 }
14851             }
14852         }
14853         if(*p == ']') *p++;
14854     }
14855
14856     while(*p == ' ') p++;
14857
14858     /* Active color */
14859     switch (*p++) {
14860       case 'w':
14861         *blackPlaysFirst = FALSE;
14862         break;
14863       case 'b': 
14864         *blackPlaysFirst = TRUE;
14865         break;
14866       default:
14867         return FALSE;
14868     }
14869
14870     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14871     /* return the extra info in global variiables             */
14872
14873     /* set defaults in case FEN is incomplete */
14874     board[EP_STATUS] = EP_UNKNOWN;
14875     for(i=0; i<nrCastlingRights; i++ ) {
14876         board[CASTLING][i] =
14877             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14878     }   /* assume possible unless obviously impossible */
14879     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14880     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14881     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14882                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14883     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14884     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14885     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14886                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14887     FENrulePlies = 0;
14888
14889     while(*p==' ') p++;
14890     if(nrCastlingRights) {
14891       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14892           /* castling indicator present, so default becomes no castlings */
14893           for(i=0; i<nrCastlingRights; i++ ) {
14894                  board[CASTLING][i] = NoRights;
14895           }
14896       }
14897       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14898              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14899              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14900              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14901         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14902
14903         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14904             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14905             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14906         }
14907         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14908             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14909         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14910                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14911         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14912                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14913         switch(c) {
14914           case'K':
14915               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14916               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14917               board[CASTLING][2] = whiteKingFile;
14918               break;
14919           case'Q':
14920               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14921               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14922               board[CASTLING][2] = whiteKingFile;
14923               break;
14924           case'k':
14925               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14926               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14927               board[CASTLING][5] = blackKingFile;
14928               break;
14929           case'q':
14930               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14931               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14932               board[CASTLING][5] = blackKingFile;
14933           case '-':
14934               break;
14935           default: /* FRC castlings */
14936               if(c >= 'a') { /* black rights */
14937                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14938                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14939                   if(i == BOARD_RGHT) break;
14940                   board[CASTLING][5] = i;
14941                   c -= AAA;
14942                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14943                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14944                   if(c > i)
14945                       board[CASTLING][3] = c;
14946                   else
14947                       board[CASTLING][4] = c;
14948               } else { /* white rights */
14949                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14950                     if(board[0][i] == WhiteKing) break;
14951                   if(i == BOARD_RGHT) break;
14952                   board[CASTLING][2] = i;
14953                   c -= AAA - 'a' + 'A';
14954                   if(board[0][c] >= WhiteKing) break;
14955                   if(c > i)
14956                       board[CASTLING][0] = c;
14957                   else
14958                       board[CASTLING][1] = c;
14959               }
14960         }
14961       }
14962       for(i=0; i<nrCastlingRights; i++)
14963         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14964     if (appData.debugMode) {
14965         fprintf(debugFP, "FEN castling rights:");
14966         for(i=0; i<nrCastlingRights; i++)
14967         fprintf(debugFP, " %d", board[CASTLING][i]);
14968         fprintf(debugFP, "\n");
14969     }
14970
14971       while(*p==' ') p++;
14972     }
14973
14974     /* read e.p. field in games that know e.p. capture */
14975     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14976        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14977       if(*p=='-') {
14978         p++; board[EP_STATUS] = EP_NONE;
14979       } else {
14980          char c = *p++ - AAA;
14981
14982          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14983          if(*p >= '0' && *p <='9') *p++;
14984          board[EP_STATUS] = c;
14985       }
14986     }
14987
14988
14989     if(sscanf(p, "%d", &i) == 1) {
14990         FENrulePlies = i; /* 50-move ply counter */
14991         /* (The move number is still ignored)    */
14992     }
14993
14994     return TRUE;
14995 }
14996       
14997 void
14998 EditPositionPasteFEN(char *fen)
14999 {
15000   if (fen != NULL) {
15001     Board initial_position;
15002
15003     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15004       DisplayError(_("Bad FEN position in clipboard"), 0);
15005       return ;
15006     } else {
15007       int savedBlackPlaysFirst = blackPlaysFirst;
15008       EditPositionEvent();
15009       blackPlaysFirst = savedBlackPlaysFirst;
15010       CopyBoard(boards[0], initial_position);
15011       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15012       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15013       DisplayBothClocks();
15014       DrawPosition(FALSE, boards[currentMove]);
15015     }
15016   }
15017 }
15018
15019 static char cseq[12] = "\\   ";
15020
15021 Boolean set_cont_sequence(char *new_seq)
15022 {
15023     int len;
15024     Boolean ret;
15025
15026     // handle bad attempts to set the sequence
15027         if (!new_seq)
15028                 return 0; // acceptable error - no debug
15029
15030     len = strlen(new_seq);
15031     ret = (len > 0) && (len < sizeof(cseq));
15032     if (ret)
15033         strcpy(cseq, new_seq);
15034     else if (appData.debugMode)
15035         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15036     return ret;
15037 }
15038
15039 /*
15040     reformat a source message so words don't cross the width boundary.  internal
15041     newlines are not removed.  returns the wrapped size (no null character unless
15042     included in source message).  If dest is NULL, only calculate the size required
15043     for the dest buffer.  lp argument indicats line position upon entry, and it's
15044     passed back upon exit.
15045 */
15046 int wrap(char *dest, char *src, int count, int width, int *lp)
15047 {
15048     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15049
15050     cseq_len = strlen(cseq);
15051     old_line = line = *lp;
15052     ansi = len = clen = 0;
15053
15054     for (i=0; i < count; i++)
15055     {
15056         if (src[i] == '\033')
15057             ansi = 1;
15058
15059         // if we hit the width, back up
15060         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15061         {
15062             // store i & len in case the word is too long
15063             old_i = i, old_len = len;
15064
15065             // find the end of the last word
15066             while (i && src[i] != ' ' && src[i] != '\n')
15067             {
15068                 i--;
15069                 len--;
15070             }
15071
15072             // word too long?  restore i & len before splitting it
15073             if ((old_i-i+clen) >= width)
15074             {
15075                 i = old_i;
15076                 len = old_len;
15077             }
15078
15079             // extra space?
15080             if (i && src[i-1] == ' ')
15081                 len--;
15082
15083             if (src[i] != ' ' && src[i] != '\n')
15084             {
15085                 i--;
15086                 if (len)
15087                     len--;
15088             }
15089
15090             // now append the newline and continuation sequence
15091             if (dest)
15092                 dest[len] = '\n';
15093             len++;
15094             if (dest)
15095                 strncpy(dest+len, cseq, cseq_len);
15096             len += cseq_len;
15097             line = cseq_len;
15098             clen = cseq_len;
15099             continue;
15100         }
15101
15102         if (dest)
15103             dest[len] = src[i];
15104         len++;
15105         if (!ansi)
15106             line++;
15107         if (src[i] == '\n')
15108             line = 0;
15109         if (src[i] == 'm')
15110             ansi = 0;
15111     }
15112     if (dest && appData.debugMode)
15113     {
15114         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15115             count, width, line, len, *lp);
15116         show_bytes(debugFP, src, count);
15117         fprintf(debugFP, "\ndest: ");
15118         show_bytes(debugFP, dest, len);
15119         fprintf(debugFP, "\n");
15120     }
15121     *lp = dest ? line : old_line;
15122
15123     return len;
15124 }
15125
15126 // [HGM] vari: routines for shelving variations
15127
15128 void 
15129 PushTail(int firstMove, int lastMove)
15130 {
15131         int i, j, nrMoves = lastMove - firstMove;
15132
15133         if(appData.icsActive) { // only in local mode
15134                 forwardMostMove = currentMove; // mimic old ICS behavior
15135                 return;
15136         }
15137         if(storedGames >= MAX_VARIATIONS-1) return;
15138
15139         // push current tail of game on stack
15140         savedResult[storedGames] = gameInfo.result;
15141         savedDetails[storedGames] = gameInfo.resultDetails;
15142         gameInfo.resultDetails = NULL;
15143         savedFirst[storedGames] = firstMove;
15144         savedLast [storedGames] = lastMove;
15145         savedFramePtr[storedGames] = framePtr;
15146         framePtr -= nrMoves; // reserve space for the boards
15147         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15148             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15149             for(j=0; j<MOVE_LEN; j++)
15150                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15151             for(j=0; j<2*MOVE_LEN; j++)
15152                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15153             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15154             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15155             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15156             pvInfoList[firstMove+i-1].depth = 0;
15157             commentList[framePtr+i] = commentList[firstMove+i];
15158             commentList[firstMove+i] = NULL;
15159         }
15160
15161         storedGames++;
15162         forwardMostMove = firstMove; // truncate game so we can start variation
15163         if(storedGames == 1) GreyRevert(FALSE);
15164 }
15165
15166 Boolean
15167 PopTail(Boolean annotate)
15168 {
15169         int i, j, nrMoves;
15170         char buf[8000], moveBuf[20];
15171
15172         if(appData.icsActive) return FALSE; // only in local mode
15173         if(!storedGames) return FALSE; // sanity
15174         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15175
15176         storedGames--;
15177         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15178         nrMoves = savedLast[storedGames] - currentMove;
15179         if(annotate) {
15180                 int cnt = 10;
15181                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15182                 else strcpy(buf, "(");
15183                 for(i=currentMove; i<forwardMostMove; i++) {
15184                         if(WhiteOnMove(i))
15185                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15186                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15187                         strcat(buf, moveBuf);
15188                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15189                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15190                 }
15191                 strcat(buf, ")");
15192         }
15193         for(i=1; i<=nrMoves; i++) { // copy last variation back
15194             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15195             for(j=0; j<MOVE_LEN; j++)
15196                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15197             for(j=0; j<2*MOVE_LEN; j++)
15198                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15199             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15200             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15201             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15202             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15203             commentList[currentMove+i] = commentList[framePtr+i];
15204             commentList[framePtr+i] = NULL;
15205         }
15206         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15207         framePtr = savedFramePtr[storedGames];
15208         gameInfo.result = savedResult[storedGames];
15209         if(gameInfo.resultDetails != NULL) {
15210             free(gameInfo.resultDetails);
15211       }
15212         gameInfo.resultDetails = savedDetails[storedGames];
15213         forwardMostMove = currentMove + nrMoves;
15214         if(storedGames == 0) GreyRevert(TRUE);
15215         return TRUE;
15216 }
15217
15218 void 
15219 CleanupTail()
15220 {       // remove all shelved variations
15221         int i;
15222         for(i=0; i<storedGames; i++) {
15223             if(savedDetails[i])
15224                 free(savedDetails[i]);
15225             savedDetails[i] = NULL;
15226         }
15227         for(i=framePtr; i<MAX_MOVES; i++) {
15228                 if(commentList[i]) free(commentList[i]);
15229                 commentList[i] = NULL;
15230         }
15231         framePtr = MAX_MOVES-1;
15232         storedGames = 0;
15233 }
15234
15235 void
15236 LoadVariation(int index, char *text)
15237 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15238         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15239         int level = 0, move;
15240
15241         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15242         // first find outermost bracketing variation
15243         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15244             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15245                 if(*p == '{') wait = '}'; else
15246                 if(*p == '[') wait = ']'; else
15247                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15248                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15249             }
15250             if(*p == wait) wait = NULLCHAR; // closing ]} found
15251             p++;
15252         }
15253         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15254         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15255         end[1] = NULLCHAR; // clip off comment beyond variation
15256         ToNrEvent(currentMove-1);
15257         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15258         // kludge: use ParsePV() to append variation to game
15259         move = currentMove;
15260         ParsePV(start, TRUE);
15261         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15262         ClearPremoveHighlights();
15263         CommentPopDown();
15264         ToNrEvent(currentMove+1);
15265 }
15266