Made the beep sounds for JAWS board navigation configurable through command-line...
[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 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 #else /* not STDC_HEADERS */
81 # if HAVE_STRING_H
82 #  include <string.h>
83 # else /* not HAVE_STRING_H */
84 #  include <strings.h>
85 # endif /* not HAVE_STRING_H */
86 #endif /* not STDC_HEADERS */
87
88 #if HAVE_SYS_FCNTL_H
89 # include <sys/fcntl.h>
90 #else /* not HAVE_SYS_FCNTL_H */
91 # if HAVE_FCNTL_H
92 #  include <fcntl.h>
93 # endif /* HAVE_FCNTL_H */
94 #endif /* not HAVE_SYS_FCNTL_H */
95
96 #if TIME_WITH_SYS_TIME
97 # include <sys/time.h>
98 # include <time.h>
99 #else
100 # if HAVE_SYS_TIME_H
101 #  include <sys/time.h>
102 # else
103 #  include <time.h>
104 # endif
105 #endif
106
107 #if defined(_amigados) && !defined(__GNUC__)
108 struct timezone {
109     int tz_minuteswest;
110     int tz_dsttime;
111 };
112 extern int gettimeofday(struct timeval *, struct timezone *);
113 #endif
114
115 #if HAVE_UNISTD_H
116 # include <unistd.h>
117 #endif
118
119 #include "common.h"
120 #include "frontend.h"
121 #include "backend.h"
122 #include "parser.h"
123 #include "moves.h"
124 #if ZIPPY
125 # include "zippy.h"
126 #endif
127 #include "backendz.h"
128 #include "gettext.h" 
129  
130 #ifdef ENABLE_NLS 
131 # define _(s) gettext (s) 
132 # define N_(s) gettext_noop (s) 
133 #else 
134 # define _(s) (s) 
135 # define N_(s) s 
136 #endif 
137
138
139 /* A point in time */
140 typedef struct {
141     long sec;  /* Assuming this is >= 32 bits */
142     int ms;    /* Assuming this is >= 16 bits */
143 } TimeMark;
144
145 int establish P((void));
146 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
147                          char *buf, int count, int error));
148 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
149                       char *buf, int count, int error));
150 void SendToICS P((char *s));
151 void SendToICSDelayed P((char *s, long msdelay));
152 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
153                       int toX, int toY));
154 void HandleMachineMove P((char *message, ChessProgramState *cps));
155 int AutoPlayOneMove P((void));
156 int LoadGameOneMove P((ChessMove readAhead));
157 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
158 int LoadPositionFromFile P((char *filename, int n, char *title));
159 int SavePositionToFile P((char *filename));
160 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
161                   Board board, char *castle, char *ep));
162 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
163 void ShowMove P((int fromX, int fromY, int toX, int toY));
164 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
165                    /*char*/int promoChar));
166 void BackwardInner P((int target));
167 void ForwardInner P((int target));
168 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
169 void EditPositionDone P((void));
170 void PrintOpponents P((FILE *fp));
171 void PrintPosition P((FILE *fp, int move));
172 void StartChessProgram P((ChessProgramState *cps));
173 void SendToProgram P((char *message, ChessProgramState *cps));
174 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
175 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
176                            char *buf, int count, int error));
177 void SendTimeControl P((ChessProgramState *cps,
178                         int mps, long tc, int inc, int sd, int st));
179 char *TimeControlTagValue P((void));
180 void Attention P((ChessProgramState *cps));
181 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
182 void ResurrectChessProgram P((void));
183 void DisplayComment P((int moveNumber, char *text));
184 void DisplayMove P((int moveNumber));
185 void DisplayAnalysis P((void));
186
187 void ParseGameHistory P((char *game));
188 void ParseBoard12 P((char *string));
189 void StartClocks P((void));
190 void SwitchClocks P((void));
191 void StopClocks P((void));
192 void ResetClocks P((void));
193 char *PGNDate P((void));
194 void SetGameInfo P((void));
195 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
196 int RegisterMove P((void));
197 void MakeRegisteredMove P((void));
198 void TruncateGame P((void));
199 int looking_at P((char *, int *, char *));
200 void CopyPlayerNameIntoFileName P((char **, char *));
201 char *SavePart P((char *));
202 int SaveGameOldStyle P((FILE *));
203 int SaveGamePGN P((FILE *));
204 void GetTimeMark P((TimeMark *));
205 long SubtractTimeMarks P((TimeMark *, TimeMark *));
206 int CheckFlags P((void));
207 long NextTickLength P((long));
208 void CheckTimeControl P((void));
209 void show_bytes P((FILE *, char *, int));
210 int string_to_rating P((char *str));
211 void ParseFeatures P((char* args, ChessProgramState *cps));
212 void InitBackEnd3 P((void));
213 void FeatureDone P((ChessProgramState* cps, int val));
214 void InitChessProgram P((ChessProgramState *cps, int setup));
215 void OutputKibitz(int window, char *text);
216 int PerpetualChase(int first, int last);
217 int EngineOutputIsUp();
218 void InitDrawingSizes(int x, int y);
219
220 #ifdef WIN32
221        extern void ConsoleCreate();
222 #endif
223
224 ChessProgramState *WhitePlayer();
225 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
226 int VerifyDisplayMode P(());
227
228 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
229 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
230 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
231 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
232 extern char installDir[MSG_SIZ];
233
234 extern int tinyLayout, smallLayout;
235 ChessProgramStats programStats;
236 static int exiting = 0; /* [HGM] moved to top */
237 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
238 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
239 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
240 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
241 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
242 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
243 int opponentKibitzes;
244 int lastSavedGame; /* [HGM] save: ID of game */
245 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
246 extern int chatCount;
247 int chattingPartner;
248
249 /* States for ics_getting_history */
250 #define H_FALSE 0
251 #define H_REQUESTED 1
252 #define H_GOT_REQ_HEADER 2
253 #define H_GOT_UNREQ_HEADER 3
254 #define H_GETTING_MOVES 4
255 #define H_GOT_UNWANTED_HEADER 5
256
257 /* whosays values for GameEnds */
258 #define GE_ICS 0
259 #define GE_ENGINE 1
260 #define GE_PLAYER 2
261 #define GE_FILE 3
262 #define GE_XBOARD 4
263 #define GE_ENGINE1 5
264 #define GE_ENGINE2 6
265
266 /* Maximum number of games in a cmail message */
267 #define CMAIL_MAX_GAMES 20
268
269 /* Different types of move when calling RegisterMove */
270 #define CMAIL_MOVE   0
271 #define CMAIL_RESIGN 1
272 #define CMAIL_DRAW   2
273 #define CMAIL_ACCEPT 3
274
275 /* Different types of result to remember for each game */
276 #define CMAIL_NOT_RESULT 0
277 #define CMAIL_OLD_RESULT 1
278 #define CMAIL_NEW_RESULT 2
279
280 /* Telnet protocol constants */
281 #define TN_WILL 0373
282 #define TN_WONT 0374
283 #define TN_DO   0375
284 #define TN_DONT 0376
285 #define TN_IAC  0377
286 #define TN_ECHO 0001
287 #define TN_SGA  0003
288 #define TN_PORT 23
289
290 /* [AS] */
291 static char * safeStrCpy( char * dst, const char * src, size_t count )
292 {
293     assert( dst != NULL );
294     assert( src != NULL );
295     assert( count > 0 );
296
297     strncpy( dst, src, count );
298     dst[ count-1 ] = '\0';
299     return dst;
300 }
301
302 #if 0
303 //[HGM] for future use? Conditioned out for now to suppress warning.
304 static char * safeStrCat( char * dst, const char * src, size_t count )
305 {
306     size_t  dst_len;
307
308     assert( dst != NULL );
309     assert( src != NULL );
310     assert( count > 0 );
311
312     dst_len = strlen(dst);
313
314     assert( count > dst_len ); /* Buffer size must be greater than current length */
315
316     safeStrCpy( dst + dst_len, src, count - dst_len );
317
318     return dst;
319 }
320 #endif
321
322 /* Some compiler can't cast u64 to double
323  * This function do the job for us:
324
325  * We use the highest bit for cast, this only
326  * works if the highest bit is not
327  * in use (This should not happen)
328  *
329  * We used this for all compiler
330  */
331 double
332 u64ToDouble(u64 value)
333 {
334   double r;
335   u64 tmp = value & u64Const(0x7fffffffffffffff);
336   r = (double)(s64)tmp;
337   if (value & u64Const(0x8000000000000000))
338        r +=  9.2233720368547758080e18; /* 2^63 */
339  return r;
340 }
341
342 /* Fake up flags for now, as we aren't keeping track of castling
343    availability yet. [HGM] Change of logic: the flag now only
344    indicates the type of castlings allowed by the rule of the game.
345    The actual rights themselves are maintained in the array
346    castlingRights, as part of the game history, and are not probed
347    by this function.
348  */
349 int
350 PosFlags(index)
351 {
352   int flags = F_ALL_CASTLE_OK;
353   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
354   switch (gameInfo.variant) {
355   case VariantSuicide:
356     flags &= ~F_ALL_CASTLE_OK;
357   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
358     flags |= F_IGNORE_CHECK;
359   case VariantLosers:
360     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
361     break;
362   case VariantAtomic:
363     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
364     break;
365   case VariantKriegspiel:
366     flags |= F_KRIEGSPIEL_CAPTURE;
367     break;
368   case VariantCapaRandom: 
369   case VariantFischeRandom:
370     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
371   case VariantNoCastle:
372   case VariantShatranj:
373   case VariantCourier:
374     flags &= ~F_ALL_CASTLE_OK;
375     break;
376   default:
377     break;
378   }
379   return flags;
380 }
381
382 FILE *gameFileFP, *debugFP;
383
384 /* 
385     [AS] Note: sometimes, the sscanf() function is used to parse the input
386     into a fixed-size buffer. Because of this, we must be prepared to
387     receive strings as long as the size of the input buffer, which is currently
388     set to 4K for Windows and 8K for the rest.
389     So, we must either allocate sufficiently large buffers here, or
390     reduce the size of the input buffer in the input reading part.
391 */
392
393 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
394 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
395 char thinkOutput1[MSG_SIZ*10];
396
397 ChessProgramState first, second;
398
399 /* premove variables */
400 int premoveToX = 0;
401 int premoveToY = 0;
402 int premoveFromX = 0;
403 int premoveFromY = 0;
404 int premovePromoChar = 0;
405 int gotPremove = 0;
406 Boolean alarmSounded;
407 /* end premove variables */
408
409 char *ics_prefix = "$";
410 int ics_type = ICS_GENERIC;
411
412 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
413 int pauseExamForwardMostMove = 0;
414 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
415 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
416 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
417 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
418 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
419 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
420 int whiteFlag = FALSE, blackFlag = FALSE;
421 int userOfferedDraw = FALSE;
422 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
423 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
424 int cmailMoveType[CMAIL_MAX_GAMES];
425 long ics_clock_paused = 0;
426 ProcRef icsPR = NoProc, cmailPR = NoProc;
427 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
428 GameMode gameMode = BeginningOfGame;
429 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
430 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
431 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
432 int hiddenThinkOutputState = 0; /* [AS] */
433 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
434 int adjudicateLossPlies = 6;
435 char white_holding[64], black_holding[64];
436 TimeMark lastNodeCountTime;
437 long lastNodeCount=0;
438 int have_sent_ICS_logon = 0;
439 int movesPerSession;
440 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
441 long timeControl_2; /* [AS] Allow separate time controls */
442 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
443 long timeRemaining[2][MAX_MOVES];
444 int matchGame = 0;
445 TimeMark programStartTime;
446 char ics_handle[MSG_SIZ];
447 int have_set_title = 0;
448
449 /* animateTraining preserves the state of appData.animate
450  * when Training mode is activated. This allows the
451  * response to be animated when appData.animate == TRUE and
452  * appData.animateDragging == TRUE.
453  */
454 Boolean animateTraining;
455
456 GameInfo gameInfo;
457
458 AppData appData;
459
460 Board boards[MAX_MOVES];
461 /* [HGM] Following 7 needed for accurate legality tests: */
462 signed char  epStatus[MAX_MOVES];
463 signed char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
464 signed char  castlingRank[BOARD_SIZE]; // and corresponding ranks
465 signed char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
466 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
467 int   initialRulePlies, FENrulePlies;
468 char  FENepStatus;
469 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
470 int loadFlag = 0; 
471 int shuffleOpenings;
472
473 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
474     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
475         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
476     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
477         BlackKing, BlackBishop, BlackKnight, BlackRook }
478 };
479
480 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
481     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
482         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
483     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
484         BlackKing, BlackKing, BlackKnight, BlackRook }
485 };
486
487 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
488     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
489         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
490     { BlackRook, BlackMan, BlackBishop, BlackQueen,
491         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
492 };
493
494 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
495     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
496         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
497     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
498         BlackKing, BlackBishop, BlackKnight, BlackRook }
499 };
500
501 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
502     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
503         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
504     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
505         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
506 };
507
508
509 #if (BOARD_SIZE>=10)
510 ChessSquare ShogiArray[2][BOARD_SIZE] = {
511     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
512         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
513     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
514         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
515 };
516
517 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
518     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
519         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
520     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
521         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
522 };
523
524 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
525     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
526         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
527     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
528         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
529 };
530
531 ChessSquare GreatArray[2][BOARD_SIZE] = {
532     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
533         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
534     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
535         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
536 };
537
538 ChessSquare JanusArray[2][BOARD_SIZE] = {
539     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
540         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
541     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
542         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
543 };
544
545 #ifdef GOTHIC
546 ChessSquare GothicArray[2][BOARD_SIZE] = {
547     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
548         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
549     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
550         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
551 };
552 #else // !GOTHIC
553 #define GothicArray CapablancaArray
554 #endif // !GOTHIC
555
556 #ifdef FALCON
557 ChessSquare FalconArray[2][BOARD_SIZE] = {
558     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
559         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
560     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
561         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
562 };
563 #else // !FALCON
564 #define FalconArray CapablancaArray
565 #endif // !FALCON
566
567 #else // !(BOARD_SIZE>=10)
568 #define XiangqiPosition FIDEArray
569 #define CapablancaArray FIDEArray
570 #define GothicArray FIDEArray
571 #define GreatArray FIDEArray
572 #endif // !(BOARD_SIZE>=10)
573
574 #if (BOARD_SIZE>=12)
575 ChessSquare CourierArray[2][BOARD_SIZE] = {
576     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
577         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
578     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
579         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
580 };
581 #else // !(BOARD_SIZE>=12)
582 #define CourierArray CapablancaArray
583 #endif // !(BOARD_SIZE>=12)
584
585
586 Board initialPosition;
587
588
589 /* Convert str to a rating. Checks for special cases of "----",
590
591    "++++", etc. Also strips ()'s */
592 int
593 string_to_rating(str)
594   char *str;
595 {
596   while(*str && !isdigit(*str)) ++str;
597   if (!*str)
598     return 0;   /* One of the special "no rating" cases */
599   else
600     return atoi(str);
601 }
602
603 void
604 ClearProgramStats()
605 {
606     /* Init programStats */
607     programStats.movelist[0] = 0;
608     programStats.depth = 0;
609     programStats.nr_moves = 0;
610     programStats.moves_left = 0;
611     programStats.nodes = 0;
612     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
613     programStats.score = 0;
614     programStats.got_only_move = 0;
615     programStats.got_fail = 0;
616     programStats.line_is_book = 0;
617 }
618
619 void
620 InitBackEnd1()
621 {
622     int matched, min, sec;
623
624     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
625
626     GetTimeMark(&programStartTime);
627     srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
628
629     ClearProgramStats();
630     programStats.ok_to_send = 1;
631     programStats.seen_stat = 0;
632
633     /*
634      * Initialize game list
635      */
636     ListNew(&gameList);
637
638
639     /*
640      * Internet chess server status
641      */
642     if (appData.icsActive) {
643         appData.matchMode = FALSE;
644         appData.matchGames = 0;
645 #if ZIPPY       
646         appData.noChessProgram = !appData.zippyPlay;
647 #else
648         appData.zippyPlay = FALSE;
649         appData.zippyTalk = FALSE;
650         appData.noChessProgram = TRUE;
651 #endif
652         if (*appData.icsHelper != NULLCHAR) {
653             appData.useTelnet = TRUE;
654             appData.telnetProgram = appData.icsHelper;
655         }
656     } else {
657         appData.zippyTalk = appData.zippyPlay = FALSE;
658     }
659
660     /* [AS] Initialize pv info list [HGM] and game state */
661     {
662         int i, j;
663
664         for( i=0; i<MAX_MOVES; i++ ) {
665             pvInfoList[i].depth = -1;
666             epStatus[i]=EP_NONE;
667             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
668         }
669     }
670
671     /*
672      * Parse timeControl resource
673      */
674     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
675                           appData.movesPerSession)) {
676         char buf[MSG_SIZ];
677         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
678         DisplayFatalError(buf, 0, 2);
679     }
680
681     /*
682      * Parse searchTime resource
683      */
684     if (*appData.searchTime != NULLCHAR) {
685         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
686         if (matched == 1) {
687             searchTime = min * 60;
688         } else if (matched == 2) {
689             searchTime = min * 60 + sec;
690         } else {
691             char buf[MSG_SIZ];
692             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
693             DisplayFatalError(buf, 0, 2);
694         }
695     }
696
697     /* [AS] Adjudication threshold */
698     adjudicateLossThreshold = appData.adjudicateLossThreshold;
699     
700     first.which = "first";
701     second.which = "second";
702     first.maybeThinking = second.maybeThinking = FALSE;
703     first.pr = second.pr = NoProc;
704     first.isr = second.isr = NULL;
705     first.sendTime = second.sendTime = 2;
706     first.sendDrawOffers = 1;
707     if (appData.firstPlaysBlack) {
708         first.twoMachinesColor = "black\n";
709         second.twoMachinesColor = "white\n";
710     } else {
711         first.twoMachinesColor = "white\n";
712         second.twoMachinesColor = "black\n";
713     }
714     first.program = appData.firstChessProgram;
715     second.program = appData.secondChessProgram;
716     first.host = appData.firstHost;
717     second.host = appData.secondHost;
718     first.dir = appData.firstDirectory;
719     second.dir = appData.secondDirectory;
720     first.other = &second;
721     second.other = &first;
722     first.initString = appData.initString;
723     second.initString = appData.secondInitString;
724     first.computerString = appData.firstComputerString;
725     second.computerString = appData.secondComputerString;
726     first.useSigint = second.useSigint = TRUE;
727     first.useSigterm = second.useSigterm = TRUE;
728     first.reuse = appData.reuseFirst;
729     second.reuse = appData.reuseSecond;
730     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
731     second.nps = appData.secondNPS;
732     first.useSetboard = second.useSetboard = FALSE;
733     first.useSAN = second.useSAN = FALSE;
734     first.usePing = second.usePing = FALSE;
735     first.lastPing = second.lastPing = 0;
736     first.lastPong = second.lastPong = 0;
737     first.usePlayother = second.usePlayother = FALSE;
738     first.useColors = second.useColors = TRUE;
739     first.useUsermove = second.useUsermove = FALSE;
740     first.sendICS = second.sendICS = FALSE;
741     first.sendName = second.sendName = appData.icsActive;
742     first.sdKludge = second.sdKludge = FALSE;
743     first.stKludge = second.stKludge = FALSE;
744     TidyProgramName(first.program, first.host, first.tidy);
745     TidyProgramName(second.program, second.host, second.tidy);
746     first.matchWins = second.matchWins = 0;
747     strcpy(first.variants, appData.variant);
748     strcpy(second.variants, appData.variant);
749     first.analysisSupport = second.analysisSupport = 2; /* detect */
750     first.analyzing = second.analyzing = FALSE;
751     first.initDone = second.initDone = FALSE;
752
753     /* New features added by Tord: */
754     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
755     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
756     /* End of new features added by Tord. */
757     first.fenOverride  = appData.fenOverride1;
758     second.fenOverride = appData.fenOverride2;
759
760     /* [HGM] time odds: set factor for each machine */
761     first.timeOdds  = appData.firstTimeOdds;
762     second.timeOdds = appData.secondTimeOdds;
763     { int norm = 1;
764         if(appData.timeOddsMode) {
765             norm = first.timeOdds;
766             if(norm > second.timeOdds) norm = second.timeOdds;
767         }
768         first.timeOdds /= norm;
769         second.timeOdds /= norm;
770     }
771
772     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
773     first.accumulateTC = appData.firstAccumulateTC;
774     second.accumulateTC = appData.secondAccumulateTC;
775     first.maxNrOfSessions = second.maxNrOfSessions = 1;
776
777     /* [HGM] debug */
778     first.debug = second.debug = FALSE;
779     first.supportsNPS = second.supportsNPS = UNKNOWN;
780
781     /* [HGM] options */
782     first.optionSettings  = appData.firstOptions;
783     second.optionSettings = appData.secondOptions;
784
785     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
786     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
787     first.isUCI = appData.firstIsUCI; /* [AS] */
788     second.isUCI = appData.secondIsUCI; /* [AS] */
789     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
790     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
791
792     if (appData.firstProtocolVersion > PROTOVER ||
793         appData.firstProtocolVersion < 1) {
794       char buf[MSG_SIZ];
795       sprintf(buf, _("protocol version %d not supported"),
796               appData.firstProtocolVersion);
797       DisplayFatalError(buf, 0, 2);
798     } else {
799       first.protocolVersion = appData.firstProtocolVersion;
800     }
801
802     if (appData.secondProtocolVersion > PROTOVER ||
803         appData.secondProtocolVersion < 1) {
804       char buf[MSG_SIZ];
805       sprintf(buf, _("protocol version %d not supported"),
806               appData.secondProtocolVersion);
807       DisplayFatalError(buf, 0, 2);
808     } else {
809       second.protocolVersion = appData.secondProtocolVersion;
810     }
811
812     if (appData.icsActive) {
813         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
814     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
815         appData.clockMode = FALSE;
816         first.sendTime = second.sendTime = 0;
817     }
818     
819 #if ZIPPY
820     /* Override some settings from environment variables, for backward
821        compatibility.  Unfortunately it's not feasible to have the env
822        vars just set defaults, at least in xboard.  Ugh.
823     */
824     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
825       ZippyInit();
826     }
827 #endif
828     
829     if (appData.noChessProgram) {
830         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
831         sprintf(programVersion, "%s", PACKAGE_STRING);
832     } else {
833 #if 0
834         char *p, *q;
835         q = first.program;
836         while (*q != ' ' && *q != NULLCHAR) q++;
837         p = q;
838         while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] backslash added */
839         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING + (q - p));
840         sprintf(programVersion, "%s + ", PACKAGE_STRING);
841         strncat(programVersion, p, q - p);
842 #else
843         /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
844         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
845         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
846 #endif
847     }
848
849     if (!appData.icsActive) {
850       char buf[MSG_SIZ];
851       /* Check for variants that are supported only in ICS mode,
852          or not at all.  Some that are accepted here nevertheless
853          have bugs; see comments below.
854       */
855       VariantClass variant = StringToVariant(appData.variant);
856       switch (variant) {
857       case VariantBughouse:     /* need four players and two boards */
858       case VariantKriegspiel:   /* need to hide pieces and move details */
859       /* case VariantFischeRandom: (Fabien: moved below) */
860         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
861         DisplayFatalError(buf, 0, 2);
862         return;
863
864       case VariantUnknown:
865       case VariantLoadable:
866       case Variant29:
867       case Variant30:
868       case Variant31:
869       case Variant32:
870       case Variant33:
871       case Variant34:
872       case Variant35:
873       case Variant36:
874       default:
875         sprintf(buf, _("Unknown variant name %s"), appData.variant);
876         DisplayFatalError(buf, 0, 2);
877         return;
878
879       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
880       case VariantFairy:      /* [HGM] TestLegality definitely off! */
881       case VariantGothic:     /* [HGM] should work */
882       case VariantCapablanca: /* [HGM] should work */
883       case VariantCourier:    /* [HGM] initial forced moves not implemented */
884       case VariantShogi:      /* [HGM] drops not tested for legality */
885       case VariantKnightmate: /* [HGM] should work */
886       case VariantCylinder:   /* [HGM] untested */
887       case VariantFalcon:     /* [HGM] untested */
888       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
889                                  offboard interposition not understood */
890       case VariantNormal:     /* definitely works! */
891       case VariantWildCastle: /* pieces not automatically shuffled */
892       case VariantNoCastle:   /* pieces not automatically shuffled */
893       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
894       case VariantLosers:     /* should work except for win condition,
895                                  and doesn't know captures are mandatory */
896       case VariantSuicide:    /* should work except for win condition,
897                                  and doesn't know captures are mandatory */
898       case VariantGiveaway:   /* should work except for win condition,
899                                  and doesn't know captures are mandatory */
900       case VariantTwoKings:   /* should work */
901       case VariantAtomic:     /* should work except for win condition */
902       case Variant3Check:     /* should work except for win condition */
903       case VariantShatranj:   /* should work except for all win conditions */
904       case VariantBerolina:   /* might work if TestLegality is off */
905       case VariantCapaRandom: /* should work */
906       case VariantJanus:      /* should work */
907       case VariantSuper:      /* experimental */
908       case VariantGreat:      /* experimental, requires legality testing to be off */
909         break;
910       }
911     }
912
913     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
914     InitEngineUCI( installDir, &second );
915 }
916
917 int NextIntegerFromString( char ** str, long * value )
918 {
919     int result = -1;
920     char * s = *str;
921
922     while( *s == ' ' || *s == '\t' ) {
923         s++;
924     }
925
926     *value = 0;
927
928     if( *s >= '0' && *s <= '9' ) {
929         while( *s >= '0' && *s <= '9' ) {
930             *value = *value * 10 + (*s - '0');
931             s++;
932         }
933
934         result = 0;
935     }
936
937     *str = s;
938
939     return result;
940 }
941
942 int NextTimeControlFromString( char ** str, long * value )
943 {
944     long temp;
945     int result = NextIntegerFromString( str, &temp );
946
947     if( result == 0 ) {
948         *value = temp * 60; /* Minutes */
949         if( **str == ':' ) {
950             (*str)++;
951             result = NextIntegerFromString( str, &temp );
952             *value += temp; /* Seconds */
953         }
954     }
955
956     return result;
957 }
958
959 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
960 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
961     int result = -1; long temp, temp2;
962
963     if(**str != '+') return -1; // old params remain in force!
964     (*str)++;
965     if( NextTimeControlFromString( str, &temp ) ) return -1;
966
967     if(**str != '/') {
968         /* time only: incremental or sudden-death time control */
969         if(**str == '+') { /* increment follows; read it */
970             (*str)++;
971             if(result = NextIntegerFromString( str, &temp2)) return -1;
972             *inc = temp2 * 1000;
973         } else *inc = 0;
974         *moves = 0; *tc = temp * 1000; 
975         return 0;
976     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
977
978     (*str)++; /* classical time control */
979     result = NextTimeControlFromString( str, &temp2);
980     if(result == 0) {
981         *moves = temp/60;
982         *tc    = temp2 * 1000;
983         *inc   = 0;
984     }
985     return result;
986 }
987
988 int GetTimeQuota(int movenr)
989 {   /* [HGM] get time to add from the multi-session time-control string */
990     int moves=1; /* kludge to force reading of first session */
991     long time, increment;
992     char *s = fullTimeControlString;
993
994     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
995     do {
996         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
997         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
998         if(movenr == -1) return time;    /* last move before new session     */
999         if(!moves) return increment;     /* current session is incremental   */
1000         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1001     } while(movenr >= -1);               /* try again for next session       */
1002
1003     return 0; // no new time quota on this move
1004 }
1005
1006 int
1007 ParseTimeControl(tc, ti, mps)
1008      char *tc;
1009      int ti;
1010      int mps;
1011 {
1012 #if 0
1013     int matched, min, sec;
1014
1015     matched = sscanf(tc, "%d:%d", &min, &sec);
1016     if (matched == 1) {
1017         timeControl = min * 60 * 1000;
1018     } else if (matched == 2) {
1019         timeControl = (min * 60 + sec) * 1000;
1020     } else {
1021         return FALSE;
1022     }
1023 #else
1024     long tc1;
1025     long tc2;
1026     char buf[MSG_SIZ];
1027
1028     if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1029     if(ti > 0) {
1030         if(mps)
1031              sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1032         else sprintf(buf, "+%s+%d", tc, ti);
1033     } else {
1034         if(mps)
1035              sprintf(buf, "+%d/%s", mps, tc);
1036         else sprintf(buf, "+%s", tc);
1037     }
1038     fullTimeControlString = StrSave(buf);
1039
1040     if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1041         return FALSE;
1042     }
1043
1044     if( *tc == '/' ) {
1045         /* Parse second time control */
1046         tc++;
1047
1048         if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1049             return FALSE;
1050         }
1051
1052         if( tc2 == 0 ) {
1053             return FALSE;
1054         }
1055
1056         timeControl_2 = tc2 * 1000;
1057     }
1058     else {
1059         timeControl_2 = 0;
1060     }
1061
1062     if( tc1 == 0 ) {
1063         return FALSE;
1064     }
1065
1066     timeControl = tc1 * 1000;
1067 #endif
1068
1069     if (ti >= 0) {
1070         timeIncrement = ti * 1000;  /* convert to ms */
1071         movesPerSession = 0;
1072     } else {
1073         timeIncrement = 0;
1074         movesPerSession = mps;
1075     }
1076     return TRUE;
1077 }
1078
1079 void
1080 InitBackEnd2()
1081 {
1082     if (appData.debugMode) {
1083         fprintf(debugFP, "%s\n", programVersion);
1084     }
1085
1086     if (appData.matchGames > 0) {
1087         appData.matchMode = TRUE;
1088     } else if (appData.matchMode) {
1089         appData.matchGames = 1;
1090     }
1091     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1092         appData.matchGames = appData.sameColorGames;
1093     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1094         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1095         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1096     }
1097     Reset(TRUE, FALSE);
1098     if (appData.noChessProgram || first.protocolVersion == 1) {
1099       InitBackEnd3();
1100     } else {
1101       /* kludge: allow timeout for initial "feature" commands */
1102       FreezeUI();
1103       DisplayMessage("", _("Starting chess program"));
1104       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1105     }
1106 }
1107
1108 void
1109 InitBackEnd3 P((void))
1110 {
1111     GameMode initialMode;
1112     char buf[MSG_SIZ];
1113     int err;
1114
1115     InitChessProgram(&first, startedFromSetupPosition);
1116
1117
1118     if (appData.icsActive) {
1119 #ifdef WIN32
1120         /* [DM] Make a console window if needed [HGM] merged ifs */
1121         ConsoleCreate(); 
1122 #endif
1123         err = establish();
1124         if (err != 0) {
1125             if (*appData.icsCommPort != NULLCHAR) {
1126                 sprintf(buf, _("Could not open comm port %s"),  
1127                         appData.icsCommPort);
1128             } else {
1129                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1130                         appData.icsHost, appData.icsPort);
1131             }
1132             DisplayFatalError(buf, err, 1);
1133             return;
1134         }
1135         SetICSMode();
1136         telnetISR =
1137           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1138         fromUserISR =
1139           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1140     } else if (appData.noChessProgram) {
1141         SetNCPMode();
1142     } else {
1143         SetGNUMode();
1144     }
1145
1146     if (*appData.cmailGameName != NULLCHAR) {
1147         SetCmailMode();
1148         OpenLoopback(&cmailPR);
1149         cmailISR =
1150           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1151     }
1152     
1153     ThawUI();
1154     DisplayMessage("", "");
1155     if (StrCaseCmp(appData.initialMode, "") == 0) {
1156       initialMode = BeginningOfGame;
1157     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1158       initialMode = TwoMachinesPlay;
1159     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1160       initialMode = AnalyzeFile; 
1161     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1162       initialMode = AnalyzeMode;
1163     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1164       initialMode = MachinePlaysWhite;
1165     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1166       initialMode = MachinePlaysBlack;
1167     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1168       initialMode = EditGame;
1169     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1170       initialMode = EditPosition;
1171     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1172       initialMode = Training;
1173     } else {
1174       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1175       DisplayFatalError(buf, 0, 2);
1176       return;
1177     }
1178
1179     if (appData.matchMode) {
1180         /* Set up machine vs. machine match */
1181         if (appData.noChessProgram) {
1182             DisplayFatalError(_("Can't have a match with no chess programs"),
1183                               0, 2);
1184             return;
1185         }
1186         matchMode = TRUE;
1187         matchGame = 1;
1188         if (*appData.loadGameFile != NULLCHAR) {
1189             int index = appData.loadGameIndex; // [HGM] autoinc
1190             if(index<0) lastIndex = index = 1;
1191             if (!LoadGameFromFile(appData.loadGameFile,
1192                                   index,
1193                                   appData.loadGameFile, FALSE)) {
1194                 DisplayFatalError(_("Bad game file"), 0, 1);
1195                 return;
1196             }
1197         } else if (*appData.loadPositionFile != NULLCHAR) {
1198             int index = appData.loadPositionIndex; // [HGM] autoinc
1199             if(index<0) lastIndex = index = 1;
1200             if (!LoadPositionFromFile(appData.loadPositionFile,
1201                                       index,
1202                                       appData.loadPositionFile)) {
1203                 DisplayFatalError(_("Bad position file"), 0, 1);
1204                 return;
1205             }
1206         }
1207         TwoMachinesEvent();
1208     } else if (*appData.cmailGameName != NULLCHAR) {
1209         /* Set up cmail mode */
1210         ReloadCmailMsgEvent(TRUE);
1211     } else {
1212         /* Set up other modes */
1213         if (initialMode == AnalyzeFile) {
1214           if (*appData.loadGameFile == NULLCHAR) {
1215             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1216             return;
1217           }
1218         }
1219         if (*appData.loadGameFile != NULLCHAR) {
1220             (void) LoadGameFromFile(appData.loadGameFile,
1221                                     appData.loadGameIndex,
1222                                     appData.loadGameFile, TRUE);
1223         } else if (*appData.loadPositionFile != NULLCHAR) {
1224             (void) LoadPositionFromFile(appData.loadPositionFile,
1225                                         appData.loadPositionIndex,
1226                                         appData.loadPositionFile);
1227             /* [HGM] try to make self-starting even after FEN load */
1228             /* to allow automatic setup of fairy variants with wtm */
1229             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1230                 gameMode = BeginningOfGame;
1231                 setboardSpoiledMachineBlack = 1;
1232             }
1233             /* [HGM] loadPos: make that every new game uses the setup */
1234             /* from file as long as we do not switch variant          */
1235             if(!blackPlaysFirst) { int i;
1236                 startedFromPositionFile = TRUE;
1237                 CopyBoard(filePosition, boards[0]);
1238                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1239             }
1240         }
1241         if (initialMode == AnalyzeMode) {
1242           if (appData.noChessProgram) {
1243             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1244             return;
1245           }
1246           if (appData.icsActive) {
1247             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1248             return;
1249           }
1250           AnalyzeModeEvent();
1251         } else if (initialMode == AnalyzeFile) {
1252           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1253           ShowThinkingEvent();
1254           AnalyzeFileEvent();
1255           AnalysisPeriodicEvent(1);
1256         } else if (initialMode == MachinePlaysWhite) {
1257           if (appData.noChessProgram) {
1258             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1259                               0, 2);
1260             return;
1261           }
1262           if (appData.icsActive) {
1263             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1264                               0, 2);
1265             return;
1266           }
1267           MachineWhiteEvent();
1268         } else if (initialMode == MachinePlaysBlack) {
1269           if (appData.noChessProgram) {
1270             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1271                               0, 2);
1272             return;
1273           }
1274           if (appData.icsActive) {
1275             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1276                               0, 2);
1277             return;
1278           }
1279           MachineBlackEvent();
1280         } else if (initialMode == TwoMachinesPlay) {
1281           if (appData.noChessProgram) {
1282             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1283                               0, 2);
1284             return;
1285           }
1286           if (appData.icsActive) {
1287             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1288                               0, 2);
1289             return;
1290           }
1291           TwoMachinesEvent();
1292         } else if (initialMode == EditGame) {
1293           EditGameEvent();
1294         } else if (initialMode == EditPosition) {
1295           EditPositionEvent();
1296         } else if (initialMode == Training) {
1297           if (*appData.loadGameFile == NULLCHAR) {
1298             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1299             return;
1300           }
1301           TrainingEvent();
1302         }
1303     }
1304 }
1305
1306 /*
1307  * Establish will establish a contact to a remote host.port.
1308  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1309  *  used to talk to the host.
1310  * Returns 0 if okay, error code if not.
1311  */
1312 int
1313 establish()
1314 {
1315     char buf[MSG_SIZ];
1316
1317     if (*appData.icsCommPort != NULLCHAR) {
1318         /* Talk to the host through a serial comm port */
1319         return OpenCommPort(appData.icsCommPort, &icsPR);
1320
1321     } else if (*appData.gateway != NULLCHAR) {
1322         if (*appData.remoteShell == NULLCHAR) {
1323             /* Use the rcmd protocol to run telnet program on a gateway host */
1324             snprintf(buf, sizeof(buf), "%s %s %s",
1325                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1326             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1327
1328         } else {
1329             /* Use the rsh program to run telnet program on a gateway host */
1330             if (*appData.remoteUser == NULLCHAR) {
1331                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1332                         appData.gateway, appData.telnetProgram,
1333                         appData.icsHost, appData.icsPort);
1334             } else {
1335                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1336                         appData.remoteShell, appData.gateway, 
1337                         appData.remoteUser, appData.telnetProgram,
1338                         appData.icsHost, appData.icsPort);
1339             }
1340             return StartChildProcess(buf, "", &icsPR);
1341
1342         }
1343     } else if (appData.useTelnet) {
1344         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1345
1346     } else {
1347         /* TCP socket interface differs somewhat between
1348            Unix and NT; handle details in the front end.
1349            */
1350         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1351     }
1352 }
1353
1354 void
1355 show_bytes(fp, buf, count)
1356      FILE *fp;
1357      char *buf;
1358      int count;
1359 {
1360     while (count--) {
1361         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1362             fprintf(fp, "\\%03o", *buf & 0xff);
1363         } else {
1364             putc(*buf, fp);
1365         }
1366         buf++;
1367     }
1368     fflush(fp);
1369 }
1370
1371 /* Returns an errno value */
1372 int
1373 OutputMaybeTelnet(pr, message, count, outError)
1374      ProcRef pr;
1375      char *message;
1376      int count;
1377      int *outError;
1378 {
1379     char buf[8192], *p, *q, *buflim;
1380     int left, newcount, outcount;
1381
1382     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1383         *appData.gateway != NULLCHAR) {
1384         if (appData.debugMode) {
1385             fprintf(debugFP, ">ICS: ");
1386             show_bytes(debugFP, message, count);
1387             fprintf(debugFP, "\n");
1388         }
1389         return OutputToProcess(pr, message, count, outError);
1390     }
1391
1392     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1393     p = message;
1394     q = buf;
1395     left = count;
1396     newcount = 0;
1397     while (left) {
1398         if (q >= buflim) {
1399             if (appData.debugMode) {
1400                 fprintf(debugFP, ">ICS: ");
1401                 show_bytes(debugFP, buf, newcount);
1402                 fprintf(debugFP, "\n");
1403             }
1404             outcount = OutputToProcess(pr, buf, newcount, outError);
1405             if (outcount < newcount) return -1; /* to be sure */
1406             q = buf;
1407             newcount = 0;
1408         }
1409         if (*p == '\n') {
1410             *q++ = '\r';
1411             newcount++;
1412         } else if (((unsigned char) *p) == TN_IAC) {
1413             *q++ = (char) TN_IAC;
1414             newcount ++;
1415         }
1416         *q++ = *p++;
1417         newcount++;
1418         left--;
1419     }
1420     if (appData.debugMode) {
1421         fprintf(debugFP, ">ICS: ");
1422         show_bytes(debugFP, buf, newcount);
1423         fprintf(debugFP, "\n");
1424     }
1425     outcount = OutputToProcess(pr, buf, newcount, outError);
1426     if (outcount < newcount) return -1; /* to be sure */
1427     return count;
1428 }
1429
1430 void
1431 read_from_player(isr, closure, message, count, error)
1432      InputSourceRef isr;
1433      VOIDSTAR closure;
1434      char *message;
1435      int count;
1436      int error;
1437 {
1438     int outError, outCount;
1439     static int gotEof = 0;
1440
1441     /* Pass data read from player on to ICS */
1442     if (count > 0) {
1443         gotEof = 0;
1444         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1445         if (outCount < count) {
1446             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1447         }
1448     } else if (count < 0) {
1449         RemoveInputSource(isr);
1450         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1451     } else if (gotEof++ > 0) {
1452         RemoveInputSource(isr);
1453         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1454     }
1455 }
1456
1457 void
1458 KeepAlive()
1459 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1460     SendToICS("date\n");
1461     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1462 }
1463
1464 void
1465 SendToICS(s)
1466      char *s;
1467 {
1468     int count, outCount, outError;
1469
1470     if (icsPR == NULL) return;
1471
1472     count = strlen(s);
1473     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1474     if (outCount < count) {
1475         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1476     }
1477 }
1478
1479 /* This is used for sending logon scripts to the ICS. Sending
1480    without a delay causes problems when using timestamp on ICC
1481    (at least on my machine). */
1482 void
1483 SendToICSDelayed(s,msdelay)
1484      char *s;
1485      long msdelay;
1486 {
1487     int count, outCount, outError;
1488
1489     if (icsPR == NULL) return;
1490
1491     count = strlen(s);
1492     if (appData.debugMode) {
1493         fprintf(debugFP, ">ICS: ");
1494         show_bytes(debugFP, s, count);
1495         fprintf(debugFP, "\n");
1496     }
1497     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1498                                       msdelay);
1499     if (outCount < count) {
1500         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1501     }
1502 }
1503
1504
1505 /* Remove all highlighting escape sequences in s
1506    Also deletes any suffix starting with '(' 
1507    */
1508 char *
1509 StripHighlightAndTitle(s)
1510      char *s;
1511 {
1512     static char retbuf[MSG_SIZ];
1513     char *p = retbuf;
1514
1515     while (*s != NULLCHAR) {
1516         while (*s == '\033') {
1517             while (*s != NULLCHAR && !isalpha(*s)) s++;
1518             if (*s != NULLCHAR) s++;
1519         }
1520         while (*s != NULLCHAR && *s != '\033') {
1521             if (*s == '(' || *s == '[') {
1522                 *p = NULLCHAR;
1523                 return retbuf;
1524             }
1525             *p++ = *s++;
1526         }
1527     }
1528     *p = NULLCHAR;
1529     return retbuf;
1530 }
1531
1532 /* Remove all highlighting escape sequences in s */
1533 char *
1534 StripHighlight(s)
1535      char *s;
1536 {
1537     static char retbuf[MSG_SIZ];
1538     char *p = retbuf;
1539
1540     while (*s != NULLCHAR) {
1541         while (*s == '\033') {
1542             while (*s != NULLCHAR && !isalpha(*s)) s++;
1543             if (*s != NULLCHAR) s++;
1544         }
1545         while (*s != NULLCHAR && *s != '\033') {
1546             *p++ = *s++;
1547         }
1548     }
1549     *p = NULLCHAR;
1550     return retbuf;
1551 }
1552
1553 char *variantNames[] = VARIANT_NAMES;
1554 char *
1555 VariantName(v)
1556      VariantClass v;
1557 {
1558     return variantNames[v];
1559 }
1560
1561
1562 /* Identify a variant from the strings the chess servers use or the
1563    PGN Variant tag names we use. */
1564 VariantClass
1565 StringToVariant(e)
1566      char *e;
1567 {
1568     char *p;
1569     int wnum = -1;
1570     VariantClass v = VariantNormal;
1571     int i, found = FALSE;
1572     char buf[MSG_SIZ];
1573
1574     if (!e) return v;
1575
1576     /* [HGM] skip over optional board-size prefixes */
1577     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1578         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1579         while( *e++ != '_');
1580     }
1581
1582     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1583       if (StrCaseStr(e, variantNames[i])) {
1584         v = (VariantClass) i;
1585         found = TRUE;
1586         break;
1587       }
1588     }
1589
1590     if (!found) {
1591       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1592           || StrCaseStr(e, "wild/fr") 
1593           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1594         v = VariantFischeRandom;
1595       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1596                  (i = 1, p = StrCaseStr(e, "w"))) {
1597         p += i;
1598         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1599         if (isdigit(*p)) {
1600           wnum = atoi(p);
1601         } else {
1602           wnum = -1;
1603         }
1604         switch (wnum) {
1605         case 0: /* FICS only, actually */
1606         case 1:
1607           /* Castling legal even if K starts on d-file */
1608           v = VariantWildCastle;
1609           break;
1610         case 2:
1611         case 3:
1612         case 4:
1613           /* Castling illegal even if K & R happen to start in
1614              normal positions. */
1615           v = VariantNoCastle;
1616           break;
1617         case 5:
1618         case 7:
1619         case 8:
1620         case 10:
1621         case 11:
1622         case 12:
1623         case 13:
1624         case 14:
1625         case 15:
1626         case 18:
1627         case 19:
1628           /* Castling legal iff K & R start in normal positions */
1629           v = VariantNormal;
1630           break;
1631         case 6:
1632         case 20:
1633         case 21:
1634           /* Special wilds for position setup; unclear what to do here */
1635           v = VariantLoadable;
1636           break;
1637         case 9:
1638           /* Bizarre ICC game */
1639           v = VariantTwoKings;
1640           break;
1641         case 16:
1642           v = VariantKriegspiel;
1643           break;
1644         case 17:
1645           v = VariantLosers;
1646           break;
1647         case 22:
1648           v = VariantFischeRandom;
1649           break;
1650         case 23:
1651           v = VariantCrazyhouse;
1652           break;
1653         case 24:
1654           v = VariantBughouse;
1655           break;
1656         case 25:
1657           v = Variant3Check;
1658           break;
1659         case 26:
1660           /* Not quite the same as FICS suicide! */
1661           v = VariantGiveaway;
1662           break;
1663         case 27:
1664           v = VariantAtomic;
1665           break;
1666         case 28:
1667           v = VariantShatranj;
1668           break;
1669
1670         /* Temporary names for future ICC types.  The name *will* change in 
1671            the next xboard/WinBoard release after ICC defines it. */
1672         case 29:
1673           v = Variant29;
1674           break;
1675         case 30:
1676           v = Variant30;
1677           break;
1678         case 31:
1679           v = Variant31;
1680           break;
1681         case 32:
1682           v = Variant32;
1683           break;
1684         case 33:
1685           v = Variant33;
1686           break;
1687         case 34:
1688           v = Variant34;
1689           break;
1690         case 35:
1691           v = Variant35;
1692           break;
1693         case 36:
1694           v = Variant36;
1695           break;
1696         case 37:
1697           v = VariantShogi;
1698           break;
1699         case 38:
1700           v = VariantXiangqi;
1701           break;
1702         case 39:
1703           v = VariantCourier;
1704           break;
1705         case 40:
1706           v = VariantGothic;
1707           break;
1708         case 41:
1709           v = VariantCapablanca;
1710           break;
1711         case 42:
1712           v = VariantKnightmate;
1713           break;
1714         case 43:
1715           v = VariantFairy;
1716           break;
1717         case 44:
1718           v = VariantCylinder;
1719           break;
1720         case 45:
1721           v = VariantFalcon;
1722           break;
1723         case 46:
1724           v = VariantCapaRandom;
1725           break;
1726         case 47:
1727           v = VariantBerolina;
1728           break;
1729         case 48:
1730           v = VariantJanus;
1731           break;
1732         case 49:
1733           v = VariantSuper;
1734           break;
1735         case 50:
1736           v = VariantGreat;
1737           break;
1738         case -1:
1739           /* Found "wild" or "w" in the string but no number;
1740              must assume it's normal chess. */
1741           v = VariantNormal;
1742           break;
1743         default:
1744           sprintf(buf, _("Unknown wild type %d"), wnum);
1745           DisplayError(buf, 0);
1746           v = VariantUnknown;
1747           break;
1748         }
1749       }
1750     }
1751     if (appData.debugMode) {
1752       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1753               e, wnum, VariantName(v));
1754     }
1755     return v;
1756 }
1757
1758 static int leftover_start = 0, leftover_len = 0;
1759 char star_match[STAR_MATCH_N][MSG_SIZ];
1760
1761 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1762    advance *index beyond it, and set leftover_start to the new value of
1763    *index; else return FALSE.  If pattern contains the character '*', it
1764    matches any sequence of characters not containing '\r', '\n', or the
1765    character following the '*' (if any), and the matched sequence(s) are
1766    copied into star_match.
1767    */
1768 int
1769 looking_at(buf, index, pattern)
1770      char *buf;
1771      int *index;
1772      char *pattern;
1773 {
1774     char *bufp = &buf[*index], *patternp = pattern;
1775     int star_count = 0;
1776     char *matchp = star_match[0];
1777     
1778     for (;;) {
1779         if (*patternp == NULLCHAR) {
1780             *index = leftover_start = bufp - buf;
1781             *matchp = NULLCHAR;
1782             return TRUE;
1783         }
1784         if (*bufp == NULLCHAR) return FALSE;
1785         if (*patternp == '*') {
1786             if (*bufp == *(patternp + 1)) {
1787                 *matchp = NULLCHAR;
1788                 matchp = star_match[++star_count];
1789                 patternp += 2;
1790                 bufp++;
1791                 continue;
1792             } else if (*bufp == '\n' || *bufp == '\r') {
1793                 patternp++;
1794                 if (*patternp == NULLCHAR)
1795                   continue;
1796                 else
1797                   return FALSE;
1798             } else {
1799                 *matchp++ = *bufp++;
1800                 continue;
1801             }
1802         }
1803         if (*patternp != *bufp) return FALSE;
1804         patternp++;
1805         bufp++;
1806     }
1807 }
1808
1809 void
1810 SendToPlayer(data, length)
1811      char *data;
1812      int length;
1813 {
1814     int error, outCount;
1815     outCount = OutputToProcess(NoProc, data, length, &error);
1816     if (outCount < length) {
1817         DisplayFatalError(_("Error writing to display"), error, 1);
1818     }
1819 }
1820
1821 void
1822 PackHolding(packed, holding)
1823      char packed[];
1824      char *holding;
1825 {
1826     char *p = holding;
1827     char *q = packed;
1828     int runlength = 0;
1829     int curr = 9999;
1830     do {
1831         if (*p == curr) {
1832             runlength++;
1833         } else {
1834             switch (runlength) {
1835               case 0:
1836                 break;
1837               case 1:
1838                 *q++ = curr;
1839                 break;
1840               case 2:
1841                 *q++ = curr;
1842                 *q++ = curr;
1843                 break;
1844               default:
1845                 sprintf(q, "%d", runlength);
1846                 while (*q) q++;
1847                 *q++ = curr;
1848                 break;
1849             }
1850             runlength = 1;
1851             curr = *p;
1852         }
1853     } while (*p++);
1854     *q = NULLCHAR;
1855 }
1856
1857 /* Telnet protocol requests from the front end */
1858 void
1859 TelnetRequest(ddww, option)
1860      unsigned char ddww, option;
1861 {
1862     unsigned char msg[3];
1863     int outCount, outError;
1864
1865     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1866
1867     if (appData.debugMode) {
1868         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1869         switch (ddww) {
1870           case TN_DO:
1871             ddwwStr = "DO";
1872             break;
1873           case TN_DONT:
1874             ddwwStr = "DONT";
1875             break;
1876           case TN_WILL:
1877             ddwwStr = "WILL";
1878             break;
1879           case TN_WONT:
1880             ddwwStr = "WONT";
1881             break;
1882           default:
1883             ddwwStr = buf1;
1884             sprintf(buf1, "%d", ddww);
1885             break;
1886         }
1887         switch (option) {
1888           case TN_ECHO:
1889             optionStr = "ECHO";
1890             break;
1891           default:
1892             optionStr = buf2;
1893             sprintf(buf2, "%d", option);
1894             break;
1895         }
1896         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1897     }
1898     msg[0] = TN_IAC;
1899     msg[1] = ddww;
1900     msg[2] = option;
1901     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1902     if (outCount < 3) {
1903         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1904     }
1905 }
1906
1907 void
1908 DoEcho()
1909 {
1910     if (!appData.icsActive) return;
1911     TelnetRequest(TN_DO, TN_ECHO);
1912 }
1913
1914 void
1915 DontEcho()
1916 {
1917     if (!appData.icsActive) return;
1918     TelnetRequest(TN_DONT, TN_ECHO);
1919 }
1920
1921 void
1922 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1923 {
1924     /* put the holdings sent to us by the server on the board holdings area */
1925     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1926     char p;
1927     ChessSquare piece;
1928
1929     if(gameInfo.holdingsWidth < 2)  return;
1930
1931     if( (int)lowestPiece >= BlackPawn ) {
1932         holdingsColumn = 0;
1933         countsColumn = 1;
1934         holdingsStartRow = BOARD_HEIGHT-1;
1935         direction = -1;
1936     } else {
1937         holdingsColumn = BOARD_WIDTH-1;
1938         countsColumn = BOARD_WIDTH-2;
1939         holdingsStartRow = 0;
1940         direction = 1;
1941     }
1942
1943     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1944         board[i][holdingsColumn] = EmptySquare;
1945         board[i][countsColumn]   = (ChessSquare) 0;
1946     }
1947     while( (p=*holdings++) != NULLCHAR ) {
1948         piece = CharToPiece( ToUpper(p) );
1949         if(piece == EmptySquare) continue;
1950         /*j = (int) piece - (int) WhitePawn;*/
1951         j = PieceToNumber(piece);
1952         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1953         if(j < 0) continue;               /* should not happen */
1954         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1955         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1956         board[holdingsStartRow+j*direction][countsColumn]++;
1957     }
1958
1959 }
1960
1961
1962 void
1963 VariantSwitch(Board board, VariantClass newVariant)
1964 {
1965    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1966    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1967 //   Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1968
1969    startedFromPositionFile = FALSE;
1970    if(gameInfo.variant == newVariant) return;
1971
1972    /* [HGM] This routine is called each time an assignment is made to
1973     * gameInfo.variant during a game, to make sure the board sizes
1974     * are set to match the new variant. If that means adding or deleting
1975     * holdings, we shift the playing board accordingly
1976     * This kludge is needed because in ICS observe mode, we get boards
1977     * of an ongoing game without knowing the variant, and learn about the
1978     * latter only later. This can be because of the move list we requested,
1979     * in which case the game history is refilled from the beginning anyway,
1980     * but also when receiving holdings of a crazyhouse game. In the latter
1981     * case we want to add those holdings to the already received position.
1982     */
1983
1984
1985   if (appData.debugMode) {
1986     fprintf(debugFP, "Switch board from %s to %s\n",
1987                VariantName(gameInfo.variant), VariantName(newVariant));
1988     setbuf(debugFP, NULL);
1989   }
1990     shuffleOpenings = 0;       /* [HGM] shuffle */
1991     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1992     switch(newVariant) {
1993             case VariantShogi:
1994               newWidth = 9;  newHeight = 9;
1995               gameInfo.holdingsSize = 7;
1996             case VariantBughouse:
1997             case VariantCrazyhouse:
1998               newHoldingsWidth = 2; break;
1999             default:
2000               newHoldingsWidth = gameInfo.holdingsSize = 0;
2001     }
2002
2003     if(newWidth  != gameInfo.boardWidth  ||
2004        newHeight != gameInfo.boardHeight ||
2005        newHoldingsWidth != gameInfo.holdingsWidth ) {
2006
2007         /* shift position to new playing area, if needed */
2008         if(newHoldingsWidth > gameInfo.holdingsWidth) {
2009            for(i=0; i<BOARD_HEIGHT; i++) 
2010                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2011                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2012                                                      board[i][j];
2013            for(i=0; i<newHeight; i++) {
2014                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2015                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2016            }
2017         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2018            for(i=0; i<BOARD_HEIGHT; i++)
2019                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2020                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2021                                                  board[i][j];
2022         }
2023
2024         gameInfo.boardWidth  = newWidth;
2025         gameInfo.boardHeight = newHeight;
2026         gameInfo.holdingsWidth = newHoldingsWidth;
2027         gameInfo.variant = newVariant;
2028         InitDrawingSizes(-2, 0);
2029
2030         /* [HGM] The following should definitely be solved in a better way */
2031 #if 0
2032         CopyBoard(board, tempBoard); /* save position in case it is board[0] */
2033         for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
2034         saveEP = epStatus[0];
2035 #endif
2036         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2037 #if 0
2038         epStatus[0] = saveEP;
2039         for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
2040         CopyBoard(tempBoard, board); /* restore position received from ICS   */
2041 #endif
2042     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2043
2044     forwardMostMove = oldForwardMostMove;
2045     backwardMostMove = oldBackwardMostMove;
2046     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2047 }
2048
2049 static int loggedOn = FALSE;
2050
2051 /*-- Game start info cache: --*/
2052 int gs_gamenum;
2053 char gs_kind[MSG_SIZ];
2054 static char player1Name[128] = "";
2055 static char player2Name[128] = "";
2056 static int player1Rating = -1;
2057 static int player2Rating = -1;
2058 /*----------------------------*/
2059
2060 ColorClass curColor = ColorNormal;
2061 int suppressKibitz = 0;
2062
2063 void
2064 read_from_ics(isr, closure, data, count, error)
2065      InputSourceRef isr;
2066      VOIDSTAR closure;
2067      char *data;
2068      int count;
2069      int error;
2070 {
2071 #define BUF_SIZE 8192
2072 #define STARTED_NONE 0
2073 #define STARTED_MOVES 1
2074 #define STARTED_BOARD 2
2075 #define STARTED_OBSERVE 3
2076 #define STARTED_HOLDINGS 4
2077 #define STARTED_CHATTER 5
2078 #define STARTED_COMMENT 6
2079 #define STARTED_MOVES_NOHIDE 7
2080     
2081     static int started = STARTED_NONE;
2082     static char parse[20000];
2083     static int parse_pos = 0;
2084     static char buf[BUF_SIZE + 1];
2085     static int firstTime = TRUE, intfSet = FALSE;
2086     static ColorClass prevColor = ColorNormal;
2087     static int savingComment = FALSE;
2088     char str[500];
2089     int i, oldi;
2090     int buf_len;
2091     int next_out;
2092     int tkind;
2093     int backup;    /* [DM] For zippy color lines */
2094     char *p;
2095     char talker[MSG_SIZ]; // [HGM] chat
2096     int channel;
2097
2098     if (appData.debugMode) {
2099       if (!error) {
2100         fprintf(debugFP, "<ICS: ");
2101         show_bytes(debugFP, data, count);
2102         fprintf(debugFP, "\n");
2103       }
2104     }
2105
2106     if (appData.debugMode) { int f = forwardMostMove;
2107         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2108                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2109     }
2110     if (count > 0) {
2111         /* If last read ended with a partial line that we couldn't parse,
2112            prepend it to the new read and try again. */
2113         if (leftover_len > 0) {
2114             for (i=0; i<leftover_len; i++)
2115               buf[i] = buf[leftover_start + i];
2116         }
2117
2118         /* Copy in new characters, removing nulls and \r's */
2119         buf_len = leftover_len;
2120         for (i = 0; i < count; i++) {
2121             if (data[i] != NULLCHAR && data[i] != '\r')
2122               buf[buf_len++] = data[i];
2123             if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && 
2124                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') {
2125                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2126                 buf[buf_len++] = ' '; // replace by space (assumes ICS does not break lines within word)
2127             }
2128         }
2129
2130         buf[buf_len] = NULLCHAR;
2131         next_out = leftover_len;
2132         leftover_start = 0;
2133         
2134         i = 0;
2135         while (i < buf_len) {
2136             /* Deal with part of the TELNET option negotiation
2137                protocol.  We refuse to do anything beyond the
2138                defaults, except that we allow the WILL ECHO option,
2139                which ICS uses to turn off password echoing when we are
2140                directly connected to it.  We reject this option
2141                if localLineEditing mode is on (always on in xboard)
2142                and we are talking to port 23, which might be a real
2143                telnet server that will try to keep WILL ECHO on permanently.
2144              */
2145             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2146                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2147                 unsigned char option;
2148                 oldi = i;
2149                 switch ((unsigned char) buf[++i]) {
2150                   case TN_WILL:
2151                     if (appData.debugMode)
2152                       fprintf(debugFP, "\n<WILL ");
2153                     switch (option = (unsigned char) buf[++i]) {
2154                       case TN_ECHO:
2155                         if (appData.debugMode)
2156                           fprintf(debugFP, "ECHO ");
2157                         /* Reply only if this is a change, according
2158                            to the protocol rules. */
2159                         if (remoteEchoOption) break;
2160                         if (appData.localLineEditing &&
2161                             atoi(appData.icsPort) == TN_PORT) {
2162                             TelnetRequest(TN_DONT, TN_ECHO);
2163                         } else {
2164                             EchoOff();
2165                             TelnetRequest(TN_DO, TN_ECHO);
2166                             remoteEchoOption = TRUE;
2167                         }
2168                         break;
2169                       default:
2170                         if (appData.debugMode)
2171                           fprintf(debugFP, "%d ", option);
2172                         /* Whatever this is, we don't want it. */
2173                         TelnetRequest(TN_DONT, option);
2174                         break;
2175                     }
2176                     break;
2177                   case TN_WONT:
2178                     if (appData.debugMode)
2179                       fprintf(debugFP, "\n<WONT ");
2180                     switch (option = (unsigned char) buf[++i]) {
2181                       case TN_ECHO:
2182                         if (appData.debugMode)
2183                           fprintf(debugFP, "ECHO ");
2184                         /* Reply only if this is a change, according
2185                            to the protocol rules. */
2186                         if (!remoteEchoOption) break;
2187                         EchoOn();
2188                         TelnetRequest(TN_DONT, TN_ECHO);
2189                         remoteEchoOption = FALSE;
2190                         break;
2191                       default:
2192                         if (appData.debugMode)
2193                           fprintf(debugFP, "%d ", (unsigned char) option);
2194                         /* Whatever this is, it must already be turned
2195                            off, because we never agree to turn on
2196                            anything non-default, so according to the
2197                            protocol rules, we don't reply. */
2198                         break;
2199                     }
2200                     break;
2201                   case TN_DO:
2202                     if (appData.debugMode)
2203                       fprintf(debugFP, "\n<DO ");
2204                     switch (option = (unsigned char) buf[++i]) {
2205                       default:
2206                         /* Whatever this is, we refuse to do it. */
2207                         if (appData.debugMode)
2208                           fprintf(debugFP, "%d ", option);
2209                         TelnetRequest(TN_WONT, option);
2210                         break;
2211                     }
2212                     break;
2213                   case TN_DONT:
2214                     if (appData.debugMode)
2215                       fprintf(debugFP, "\n<DONT ");
2216                     switch (option = (unsigned char) buf[++i]) {
2217                       default:
2218                         if (appData.debugMode)
2219                           fprintf(debugFP, "%d ", option);
2220                         /* Whatever this is, we are already not doing
2221                            it, because we never agree to do anything
2222                            non-default, so according to the protocol
2223                            rules, we don't reply. */
2224                         break;
2225                     }
2226                     break;
2227                   case TN_IAC:
2228                     if (appData.debugMode)
2229                       fprintf(debugFP, "\n<IAC ");
2230                     /* Doubled IAC; pass it through */
2231                     i--;
2232                     break;
2233                   default:
2234                     if (appData.debugMode)
2235                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2236                     /* Drop all other telnet commands on the floor */
2237                     break;
2238                 }
2239                 if (oldi > next_out)
2240                   SendToPlayer(&buf[next_out], oldi - next_out);
2241                 if (++i > next_out)
2242                   next_out = i;
2243                 continue;
2244             }
2245                 
2246             /* OK, this at least will *usually* work */
2247             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2248                 loggedOn = TRUE;
2249             }
2250             
2251             if (loggedOn && !intfSet) {
2252                 if (ics_type == ICS_ICC) {
2253                   sprintf(str,
2254                           "/set-quietly interface %s\n/set-quietly style 12\n",
2255                           programVersion);
2256
2257                 } else if (ics_type == ICS_CHESSNET) {
2258                   sprintf(str, "/style 12\n");
2259                 } else {
2260                   strcpy(str, "alias $ @\n$set interface ");
2261                   strcat(str, programVersion);
2262                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2263 #ifdef WIN32
2264                   strcat(str, "$iset nohighlight 1\n");
2265 #endif
2266                   strcat(str, "$iset lock 1\n$style 12\n");
2267                 }
2268                 SendToICS(str);
2269                 intfSet = TRUE;
2270             }
2271
2272             if (started == STARTED_COMMENT) {
2273                 /* Accumulate characters in comment */
2274                 parse[parse_pos++] = buf[i];
2275                 if (buf[i] == '\n') {
2276                     parse[parse_pos] = NULLCHAR;
2277                     if(chattingPartner>=0) {
2278                         char mess[MSG_SIZ];
2279                         sprintf(mess, "%s%s", talker, parse);
2280                         OutputChatMessage(chattingPartner, mess);
2281                         chattingPartner = -1;
2282                     } else
2283                     if(!suppressKibitz) // [HGM] kibitz
2284                         AppendComment(forwardMostMove, StripHighlight(parse));
2285                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2286                         int nrDigit = 0, nrAlph = 0, i;
2287                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2288                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2289                         parse[parse_pos] = NULLCHAR;
2290                         // try to be smart: if it does not look like search info, it should go to
2291                         // ICS interaction window after all, not to engine-output window.
2292                         for(i=0; i<parse_pos; i++) { // count letters and digits
2293                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2294                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2295                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2296                         }
2297                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2298                             int depth=0; float score;
2299                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2300                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2301                                 pvInfoList[forwardMostMove-1].depth = depth;
2302                                 pvInfoList[forwardMostMove-1].score = 100*score;
2303                             }
2304                             OutputKibitz(suppressKibitz, parse);
2305                         } else {
2306                             char tmp[MSG_SIZ];
2307                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2308                             SendToPlayer(tmp, strlen(tmp));
2309                         }
2310                     }
2311                     started = STARTED_NONE;
2312                 } else {
2313                     /* Don't match patterns against characters in chatter */
2314                     i++;
2315                     continue;
2316                 }
2317             }
2318             if (started == STARTED_CHATTER) {
2319                 if (buf[i] != '\n') {
2320                     /* Don't match patterns against characters in chatter */
2321                     i++;
2322                     continue;
2323                 }
2324                 started = STARTED_NONE;
2325             }
2326
2327             /* Kludge to deal with rcmd protocol */
2328             if (firstTime && looking_at(buf, &i, "\001*")) {
2329                 DisplayFatalError(&buf[1], 0, 1);
2330                 continue;
2331             } else {
2332                 firstTime = FALSE;
2333             }
2334
2335             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2336                 ics_type = ICS_ICC;
2337                 ics_prefix = "/";
2338                 if (appData.debugMode)
2339                   fprintf(debugFP, "ics_type %d\n", ics_type);
2340                 continue;
2341             }
2342             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2343                 ics_type = ICS_FICS;
2344                 ics_prefix = "$";
2345                 if (appData.debugMode)
2346                   fprintf(debugFP, "ics_type %d\n", ics_type);
2347                 continue;
2348             }
2349             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2350                 ics_type = ICS_CHESSNET;
2351                 ics_prefix = "/";
2352                 if (appData.debugMode)
2353                   fprintf(debugFP, "ics_type %d\n", ics_type);
2354                 continue;
2355             }
2356
2357             if (!loggedOn &&
2358                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2359                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2360                  looking_at(buf, &i, "will be \"*\""))) {
2361               strcpy(ics_handle, star_match[0]);
2362               continue;
2363             }
2364
2365             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2366               char buf[MSG_SIZ];
2367               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2368               DisplayIcsInteractionTitle(buf);
2369               have_set_title = TRUE;
2370             }
2371
2372             /* skip finger notes */
2373             if (started == STARTED_NONE &&
2374                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2375                  (buf[i] == '1' && buf[i+1] == '0')) &&
2376                 buf[i+2] == ':' && buf[i+3] == ' ') {
2377               started = STARTED_CHATTER;
2378               i += 3;
2379               continue;
2380             }
2381
2382             /* skip formula vars */
2383             if (started == STARTED_NONE &&
2384                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2385               started = STARTED_CHATTER;
2386               i += 3;
2387               continue;
2388             }
2389
2390             oldi = i;
2391             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2392             if (appData.autoKibitz && started == STARTED_NONE && 
2393                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2394                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2395                 if(looking_at(buf, &i, "* kibitzes: ") &&
2396                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2397                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2398                         suppressKibitz = TRUE;
2399                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2400                                 && (gameMode == IcsPlayingWhite)) ||
2401                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2402                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2403                             started = STARTED_CHATTER; // own kibitz we simply discard
2404                         else {
2405                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2406                             parse_pos = 0; parse[0] = NULLCHAR;
2407                             savingComment = TRUE;
2408                             suppressKibitz = gameMode != IcsObserving ? 2 :
2409                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2410                         } 
2411                         continue;
2412                 } else
2413                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2414                     started = STARTED_CHATTER;
2415                     suppressKibitz = TRUE;
2416                 }
2417             } // [HGM] kibitz: end of patch
2418
2419 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2420
2421             // [HGM] chat: intercept tells by users for which we have an open chat window
2422             channel = -1;
2423             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2424                                            looking_at(buf, &i, "* whispers:") ||
2425                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2426                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2427                 int p;
2428                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2429                 chattingPartner = -1;
2430
2431                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2432                 for(p=0; p<MAX_CHAT; p++) {
2433                     if(channel == atoi(chatPartner[p])) {
2434                     talker[0] = '['; strcat(talker, "]");
2435                     chattingPartner = p; break;
2436                     }
2437                 } else
2438                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2439                 for(p=0; p<MAX_CHAT; p++) {
2440                     if(!strcmp("WHISPER", chatPartner[p])) {
2441                         talker[0] = '['; strcat(talker, "]");
2442                         chattingPartner = p; break;
2443                     }
2444                 }
2445                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2446                 for(p=0; p<MAX_CHAT; p++) if(!strcasecmp(talker+1, chatPartner[p])) {
2447                     talker[0] = 0;
2448                     chattingPartner = p; break;
2449                 }
2450                 if(chattingPartner<0) i = oldi; else {
2451                     started = STARTED_COMMENT;
2452                     parse_pos = 0; parse[0] = NULLCHAR;
2453                     savingComment = TRUE;
2454                     suppressKibitz = TRUE;
2455                 }
2456             } // [HGM] chat: end of patch
2457
2458             if (appData.zippyTalk || appData.zippyPlay) {
2459                 /* [DM] Backup address for color zippy lines */
2460                 backup = i;
2461 #if ZIPPY
2462        #ifdef WIN32
2463                if (loggedOn == TRUE)
2464                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2465                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2466        #else
2467                 if (ZippyControl(buf, &i) ||
2468                     ZippyConverse(buf, &i) ||
2469                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2470                       loggedOn = TRUE;
2471                       if (!appData.colorize) continue;
2472                 }
2473        #endif
2474 #endif
2475             } // [DM] 'else { ' deleted
2476                 if (
2477                     /* Regular tells and says */
2478                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2479                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2480                     looking_at(buf, &i, "* says: ") ||
2481                     /* Don't color "message" or "messages" output */
2482                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2483                     looking_at(buf, &i, "*. * at *:*: ") ||
2484                     looking_at(buf, &i, "--* (*:*): ") ||
2485                     /* Message notifications (same color as tells) */
2486                     looking_at(buf, &i, "* has left a message ") ||
2487                     looking_at(buf, &i, "* just sent you a message:\n") ||
2488                     /* Whispers and kibitzes */
2489                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2490                     looking_at(buf, &i, "* kibitzes: ") ||
2491                     /* Channel tells */
2492                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2493
2494                   if (tkind == 1 && strchr(star_match[0], ':')) {
2495                       /* Avoid "tells you:" spoofs in channels */
2496                      tkind = 3;
2497                   }
2498                   if (star_match[0][0] == NULLCHAR ||
2499                       strchr(star_match[0], ' ') ||
2500                       (tkind == 3 && strchr(star_match[1], ' '))) {
2501                     /* Reject bogus matches */
2502                     i = oldi;
2503                   } else {
2504                     if (appData.colorize) {
2505                       if (oldi > next_out) {
2506                         SendToPlayer(&buf[next_out], oldi - next_out);
2507                         next_out = oldi;
2508                       }
2509                       switch (tkind) {
2510                       case 1:
2511                         Colorize(ColorTell, FALSE);
2512                         curColor = ColorTell;
2513                         break;
2514                       case 2:
2515                         Colorize(ColorKibitz, FALSE);
2516                         curColor = ColorKibitz;
2517                         break;
2518                       case 3:
2519                         p = strrchr(star_match[1], '(');
2520                         if (p == NULL) {
2521                           p = star_match[1];
2522                         } else {
2523                           p++;
2524                         }
2525                         if (atoi(p) == 1) {
2526                           Colorize(ColorChannel1, FALSE);
2527                           curColor = ColorChannel1;
2528                         } else {
2529                           Colorize(ColorChannel, FALSE);
2530                           curColor = ColorChannel;
2531                         }
2532                         break;
2533                       case 5:
2534                         curColor = ColorNormal;
2535                         break;
2536                       }
2537                     }
2538                     if (started == STARTED_NONE && appData.autoComment &&
2539                         (gameMode == IcsObserving ||
2540                          gameMode == IcsPlayingWhite ||
2541                          gameMode == IcsPlayingBlack)) {
2542                       parse_pos = i - oldi;
2543                       memcpy(parse, &buf[oldi], parse_pos);
2544                       parse[parse_pos] = NULLCHAR;
2545                       started = STARTED_COMMENT;
2546                       savingComment = TRUE;
2547                     } else {
2548                       started = STARTED_CHATTER;
2549                       savingComment = FALSE;
2550                     }
2551                     loggedOn = TRUE;
2552                     continue;
2553                   }
2554                 }
2555
2556                 if (looking_at(buf, &i, "* s-shouts: ") ||
2557                     looking_at(buf, &i, "* c-shouts: ")) {
2558                     if (appData.colorize) {
2559                         if (oldi > next_out) {
2560                             SendToPlayer(&buf[next_out], oldi - next_out);
2561                             next_out = oldi;
2562                         }
2563                         Colorize(ColorSShout, FALSE);
2564                         curColor = ColorSShout;
2565                     }
2566                     loggedOn = TRUE;
2567                     started = STARTED_CHATTER;
2568                     continue;
2569                 }
2570
2571                 if (looking_at(buf, &i, "--->")) {
2572                     loggedOn = TRUE;
2573                     continue;
2574                 }
2575
2576                 if (looking_at(buf, &i, "* shouts: ") ||
2577                     looking_at(buf, &i, "--> ")) {
2578                     if (appData.colorize) {
2579                         if (oldi > next_out) {
2580                             SendToPlayer(&buf[next_out], oldi - next_out);
2581                             next_out = oldi;
2582                         }
2583                         Colorize(ColorShout, FALSE);
2584                         curColor = ColorShout;
2585                     }
2586                     loggedOn = TRUE;
2587                     started = STARTED_CHATTER;
2588                     continue;
2589                 }
2590
2591                 if (looking_at( buf, &i, "Challenge:")) {
2592                     if (appData.colorize) {
2593                         if (oldi > next_out) {
2594                             SendToPlayer(&buf[next_out], oldi - next_out);
2595                             next_out = oldi;
2596                         }
2597                         Colorize(ColorChallenge, FALSE);
2598                         curColor = ColorChallenge;
2599                     }
2600                     loggedOn = TRUE;
2601                     continue;
2602                 }
2603
2604                 if (looking_at(buf, &i, "* offers you") ||
2605                     looking_at(buf, &i, "* offers to be") ||
2606                     looking_at(buf, &i, "* would like to") ||
2607                     looking_at(buf, &i, "* requests to") ||
2608                     looking_at(buf, &i, "Your opponent offers") ||
2609                     looking_at(buf, &i, "Your opponent requests")) {
2610
2611                     if (appData.colorize) {
2612                         if (oldi > next_out) {
2613                             SendToPlayer(&buf[next_out], oldi - next_out);
2614                             next_out = oldi;
2615                         }
2616                         Colorize(ColorRequest, FALSE);
2617                         curColor = ColorRequest;
2618                     }
2619                     continue;
2620                 }
2621
2622                 if (looking_at(buf, &i, "* (*) seeking")) {
2623                     if (appData.colorize) {
2624                         if (oldi > next_out) {
2625                             SendToPlayer(&buf[next_out], oldi - next_out);
2626                             next_out = oldi;
2627                         }
2628                         Colorize(ColorSeek, FALSE);
2629                         curColor = ColorSeek;
2630                     }
2631                     continue;
2632             }
2633
2634             if (looking_at(buf, &i, "\\   ")) {
2635                 if (prevColor != ColorNormal) {
2636                     if (oldi > next_out) {
2637                         SendToPlayer(&buf[next_out], oldi - next_out);
2638                         next_out = oldi;
2639                     }
2640                     Colorize(prevColor, TRUE);
2641                     curColor = prevColor;
2642                 }
2643                 if (savingComment) {
2644                     parse_pos = i - oldi;
2645                     memcpy(parse, &buf[oldi], parse_pos);
2646                     parse[parse_pos] = NULLCHAR;
2647                     started = STARTED_COMMENT;
2648                 } else {
2649                     started = STARTED_CHATTER;
2650                 }
2651                 continue;
2652             }
2653
2654             if (looking_at(buf, &i, "Black Strength :") ||
2655                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2656                 looking_at(buf, &i, "<10>") ||
2657                 looking_at(buf, &i, "#@#")) {
2658                 /* Wrong board style */
2659                 loggedOn = TRUE;
2660                 SendToICS(ics_prefix);
2661                 SendToICS("set style 12\n");
2662                 SendToICS(ics_prefix);
2663                 SendToICS("refresh\n");
2664                 continue;
2665             }
2666             
2667             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2668                 ICSInitScript();
2669                 have_sent_ICS_logon = 1;
2670                 continue;
2671             }
2672               
2673             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2674                 (looking_at(buf, &i, "\n<12> ") ||
2675                  looking_at(buf, &i, "<12> "))) {
2676                 loggedOn = TRUE;
2677                 if (oldi > next_out) {
2678                     SendToPlayer(&buf[next_out], oldi - next_out);
2679                 }
2680                 next_out = i;
2681                 started = STARTED_BOARD;
2682                 parse_pos = 0;
2683                 continue;
2684             }
2685
2686             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2687                 looking_at(buf, &i, "<b1> ")) {
2688                 if (oldi > next_out) {
2689                     SendToPlayer(&buf[next_out], oldi - next_out);
2690                 }
2691                 next_out = i;
2692                 started = STARTED_HOLDINGS;
2693                 parse_pos = 0;
2694                 continue;
2695             }
2696
2697             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2698                 loggedOn = TRUE;
2699                 /* Header for a move list -- first line */
2700
2701                 switch (ics_getting_history) {
2702                   case H_FALSE:
2703                     switch (gameMode) {
2704                       case IcsIdle:
2705                       case BeginningOfGame:
2706                         /* User typed "moves" or "oldmoves" while we
2707                            were idle.  Pretend we asked for these
2708                            moves and soak them up so user can step
2709                            through them and/or save them.
2710                            */
2711                         Reset(FALSE, TRUE);
2712                         gameMode = IcsObserving;
2713                         ModeHighlight();
2714                         ics_gamenum = -1;
2715                         ics_getting_history = H_GOT_UNREQ_HEADER;
2716                         break;
2717                       case EditGame: /*?*/
2718                       case EditPosition: /*?*/
2719                         /* Should above feature work in these modes too? */
2720                         /* For now it doesn't */
2721                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2722                         break;
2723                       default:
2724                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2725                         break;
2726                     }
2727                     break;
2728                   case H_REQUESTED:
2729                     /* Is this the right one? */
2730                     if (gameInfo.white && gameInfo.black &&
2731                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2732                         strcmp(gameInfo.black, star_match[2]) == 0) {
2733                         /* All is well */
2734                         ics_getting_history = H_GOT_REQ_HEADER;
2735                     }
2736                     break;
2737                   case H_GOT_REQ_HEADER:
2738                   case H_GOT_UNREQ_HEADER:
2739                   case H_GOT_UNWANTED_HEADER:
2740                   case H_GETTING_MOVES:
2741                     /* Should not happen */
2742                     DisplayError(_("Error gathering move list: two headers"), 0);
2743                     ics_getting_history = H_FALSE;
2744                     break;
2745                 }
2746
2747                 /* Save player ratings into gameInfo if needed */
2748                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2749                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2750                     (gameInfo.whiteRating == -1 ||
2751                      gameInfo.blackRating == -1)) {
2752
2753                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2754                     gameInfo.blackRating = string_to_rating(star_match[3]);
2755                     if (appData.debugMode)
2756                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2757                               gameInfo.whiteRating, gameInfo.blackRating);
2758                 }
2759                 continue;
2760             }
2761
2762             if (looking_at(buf, &i,
2763               "* * match, initial time: * minute*, increment: * second")) {
2764                 /* Header for a move list -- second line */
2765                 /* Initial board will follow if this is a wild game */
2766                 if (gameInfo.event != NULL) free(gameInfo.event);
2767                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2768                 gameInfo.event = StrSave(str);
2769                 /* [HGM] we switched variant. Translate boards if needed. */
2770                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2771                 continue;
2772             }
2773
2774             if (looking_at(buf, &i, "Move  ")) {
2775                 /* Beginning of a move list */
2776                 switch (ics_getting_history) {
2777                   case H_FALSE:
2778                     /* Normally should not happen */
2779                     /* Maybe user hit reset while we were parsing */
2780                     break;
2781                   case H_REQUESTED:
2782                     /* Happens if we are ignoring a move list that is not
2783                      * the one we just requested.  Common if the user
2784                      * tries to observe two games without turning off
2785                      * getMoveList */
2786                     break;
2787                   case H_GETTING_MOVES:
2788                     /* Should not happen */
2789                     DisplayError(_("Error gathering move list: nested"), 0);
2790                     ics_getting_history = H_FALSE;
2791                     break;
2792                   case H_GOT_REQ_HEADER:
2793                     ics_getting_history = H_GETTING_MOVES;
2794                     started = STARTED_MOVES;
2795                     parse_pos = 0;
2796                     if (oldi > next_out) {
2797                         SendToPlayer(&buf[next_out], oldi - next_out);
2798                     }
2799                     break;
2800                   case H_GOT_UNREQ_HEADER:
2801                     ics_getting_history = H_GETTING_MOVES;
2802                     started = STARTED_MOVES_NOHIDE;
2803                     parse_pos = 0;
2804                     break;
2805                   case H_GOT_UNWANTED_HEADER:
2806                     ics_getting_history = H_FALSE;
2807                     break;
2808                 }
2809                 continue;
2810             }                           
2811             
2812             if (looking_at(buf, &i, "% ") ||
2813                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2814                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2815                 savingComment = FALSE;
2816                 switch (started) {
2817                   case STARTED_MOVES:
2818                   case STARTED_MOVES_NOHIDE:
2819                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2820                     parse[parse_pos + i - oldi] = NULLCHAR;
2821                     ParseGameHistory(parse);
2822 #if ZIPPY
2823                     if (appData.zippyPlay && first.initDone) {
2824                         FeedMovesToProgram(&first, forwardMostMove);
2825                         if (gameMode == IcsPlayingWhite) {
2826                             if (WhiteOnMove(forwardMostMove)) {
2827                                 if (first.sendTime) {
2828                                   if (first.useColors) {
2829                                     SendToProgram("black\n", &first); 
2830                                   }
2831                                   SendTimeRemaining(&first, TRUE);
2832                                 }
2833 #if 0
2834                                 if (first.useColors) {
2835                                   SendToProgram("white\ngo\n", &first);
2836                                 } else {
2837                                   SendToProgram("go\n", &first);
2838                                 }
2839 #else
2840                                 if (first.useColors) {
2841                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2842                                 }
2843                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2844 #endif
2845                                 first.maybeThinking = TRUE;
2846                             } else {
2847                                 if (first.usePlayother) {
2848                                   if (first.sendTime) {
2849                                     SendTimeRemaining(&first, TRUE);
2850                                   }
2851                                   SendToProgram("playother\n", &first);
2852                                   firstMove = FALSE;
2853                                 } else {
2854                                   firstMove = TRUE;
2855                                 }
2856                             }
2857                         } else if (gameMode == IcsPlayingBlack) {
2858                             if (!WhiteOnMove(forwardMostMove)) {
2859                                 if (first.sendTime) {
2860                                   if (first.useColors) {
2861                                     SendToProgram("white\n", &first);
2862                                   }
2863                                   SendTimeRemaining(&first, FALSE);
2864                                 }
2865 #if 0
2866                                 if (first.useColors) {
2867                                   SendToProgram("black\ngo\n", &first);
2868                                 } else {
2869                                   SendToProgram("go\n", &first);
2870                                 }
2871 #else
2872                                 if (first.useColors) {
2873                                   SendToProgram("black\n", &first);
2874                                 }
2875                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2876 #endif
2877                                 first.maybeThinking = TRUE;
2878                             } else {
2879                                 if (first.usePlayother) {
2880                                   if (first.sendTime) {
2881                                     SendTimeRemaining(&first, FALSE);
2882                                   }
2883                                   SendToProgram("playother\n", &first);
2884                                   firstMove = FALSE;
2885                                 } else {
2886                                   firstMove = TRUE;
2887                                 }
2888                             }
2889                         }                       
2890                     }
2891 #endif
2892                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2893                         /* Moves came from oldmoves or moves command
2894                            while we weren't doing anything else.
2895                            */
2896                         currentMove = forwardMostMove;
2897                         ClearHighlights();/*!!could figure this out*/
2898                         flipView = appData.flipView;
2899                         DrawPosition(FALSE, boards[currentMove]);
2900                         DisplayBothClocks();
2901                         sprintf(str, "%s vs. %s",
2902                                 gameInfo.white, gameInfo.black);
2903                         DisplayTitle(str);
2904                         gameMode = IcsIdle;
2905                     } else {
2906                         /* Moves were history of an active game */
2907                         if (gameInfo.resultDetails != NULL) {
2908                             free(gameInfo.resultDetails);
2909                             gameInfo.resultDetails = NULL;
2910                         }
2911                     }
2912                     HistorySet(parseList, backwardMostMove,
2913                                forwardMostMove, currentMove-1);
2914                     DisplayMove(currentMove - 1);
2915                     if (started == STARTED_MOVES) next_out = i;
2916                     started = STARTED_NONE;
2917                     ics_getting_history = H_FALSE;
2918                     break;
2919
2920                   case STARTED_OBSERVE:
2921                     started = STARTED_NONE;
2922                     SendToICS(ics_prefix);
2923                     SendToICS("refresh\n");
2924                     break;
2925
2926                   default:
2927                     break;
2928                 }
2929                 if(bookHit) { // [HGM] book: simulate book reply
2930                     static char bookMove[MSG_SIZ]; // a bit generous?
2931
2932                     programStats.nodes = programStats.depth = programStats.time = 
2933                     programStats.score = programStats.got_only_move = 0;
2934                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2935
2936                     strcpy(bookMove, "move ");
2937                     strcat(bookMove, bookHit);
2938                     HandleMachineMove(bookMove, &first);
2939                 }
2940                 continue;
2941             }
2942             
2943             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2944                  started == STARTED_HOLDINGS ||
2945                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2946                 /* Accumulate characters in move list or board */
2947                 parse[parse_pos++] = buf[i];
2948             }
2949             
2950             /* Start of game messages.  Mostly we detect start of game
2951                when the first board image arrives.  On some versions
2952                of the ICS, though, we need to do a "refresh" after starting
2953                to observe in order to get the current board right away. */
2954             if (looking_at(buf, &i, "Adding game * to observation list")) {
2955                 started = STARTED_OBSERVE;
2956                 continue;
2957             }
2958
2959             /* Handle auto-observe */
2960             if (appData.autoObserve &&
2961                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2962                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2963                 char *player;
2964                 /* Choose the player that was highlighted, if any. */
2965                 if (star_match[0][0] == '\033' ||
2966                     star_match[1][0] != '\033') {
2967                     player = star_match[0];
2968                 } else {
2969                     player = star_match[2];
2970                 }
2971                 sprintf(str, "%sobserve %s\n",
2972                         ics_prefix, StripHighlightAndTitle(player));
2973                 SendToICS(str);
2974
2975                 /* Save ratings from notify string */
2976                 strcpy(player1Name, star_match[0]);
2977                 player1Rating = string_to_rating(star_match[1]);
2978                 strcpy(player2Name, star_match[2]);
2979                 player2Rating = string_to_rating(star_match[3]);
2980
2981                 if (appData.debugMode)
2982                   fprintf(debugFP, 
2983                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2984                           player1Name, player1Rating,
2985                           player2Name, player2Rating);
2986
2987                 continue;
2988             }
2989
2990             /* Deal with automatic examine mode after a game,
2991                and with IcsObserving -> IcsExamining transition */
2992             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2993                 looking_at(buf, &i, "has made you an examiner of game *")) {
2994
2995                 int gamenum = atoi(star_match[0]);
2996                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2997                     gamenum == ics_gamenum) {
2998                     /* We were already playing or observing this game;
2999                        no need to refetch history */
3000                     gameMode = IcsExamining;
3001                     if (pausing) {
3002                         pauseExamForwardMostMove = forwardMostMove;
3003                     } else if (currentMove < forwardMostMove) {
3004                         ForwardInner(forwardMostMove);
3005                     }
3006                 } else {
3007                     /* I don't think this case really can happen */
3008                     SendToICS(ics_prefix);
3009                     SendToICS("refresh\n");
3010                 }
3011                 continue;
3012             }    
3013             
3014             /* Error messages */
3015 //          if (ics_user_moved) {
3016             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3017                 if (looking_at(buf, &i, "Illegal move") ||
3018                     looking_at(buf, &i, "Not a legal move") ||
3019                     looking_at(buf, &i, "Your king is in check") ||
3020                     looking_at(buf, &i, "It isn't your turn") ||
3021                     looking_at(buf, &i, "It is not your move")) {
3022                     /* Illegal move */
3023                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3024                         currentMove = --forwardMostMove;
3025                         DisplayMove(currentMove - 1); /* before DMError */
3026                         DrawPosition(FALSE, boards[currentMove]);
3027                         SwitchClocks();
3028                         DisplayBothClocks();
3029                     }
3030                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3031                     ics_user_moved = 0;
3032                     continue;
3033                 }
3034             }
3035
3036             if (looking_at(buf, &i, "still have time") ||
3037                 looking_at(buf, &i, "not out of time") ||
3038                 looking_at(buf, &i, "either player is out of time") ||
3039                 looking_at(buf, &i, "has timeseal; checking")) {
3040                 /* We must have called his flag a little too soon */
3041                 whiteFlag = blackFlag = FALSE;
3042                 continue;
3043             }
3044
3045             if (looking_at(buf, &i, "added * seconds to") ||
3046                 looking_at(buf, &i, "seconds were added to")) {
3047                 /* Update the clocks */
3048                 SendToICS(ics_prefix);
3049                 SendToICS("refresh\n");
3050                 continue;
3051             }
3052
3053             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3054                 ics_clock_paused = TRUE;
3055                 StopClocks();
3056                 continue;
3057             }
3058
3059             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3060                 ics_clock_paused = FALSE;
3061                 StartClocks();
3062                 continue;
3063             }
3064
3065             /* Grab player ratings from the Creating: message.
3066                Note we have to check for the special case when
3067                the ICS inserts things like [white] or [black]. */
3068             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3069                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3070                 /* star_matches:
3071                    0    player 1 name (not necessarily white)
3072                    1    player 1 rating
3073                    2    empty, white, or black (IGNORED)
3074                    3    player 2 name (not necessarily black)
3075                    4    player 2 rating
3076                    
3077                    The names/ratings are sorted out when the game
3078                    actually starts (below).
3079                 */
3080                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3081                 player1Rating = string_to_rating(star_match[1]);
3082                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3083                 player2Rating = string_to_rating(star_match[4]);
3084
3085                 if (appData.debugMode)
3086                   fprintf(debugFP, 
3087                           "Ratings from 'Creating:' %s %d, %s %d\n",
3088                           player1Name, player1Rating,
3089                           player2Name, player2Rating);
3090
3091                 continue;
3092             }
3093             
3094             /* Improved generic start/end-of-game messages */
3095             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3096                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3097                 /* If tkind == 0: */
3098                 /* star_match[0] is the game number */
3099                 /*           [1] is the white player's name */
3100                 /*           [2] is the black player's name */
3101                 /* For end-of-game: */
3102                 /*           [3] is the reason for the game end */
3103                 /*           [4] is a PGN end game-token, preceded by " " */
3104                 /* For start-of-game: */
3105                 /*           [3] begins with "Creating" or "Continuing" */
3106                 /*           [4] is " *" or empty (don't care). */
3107                 int gamenum = atoi(star_match[0]);
3108                 char *whitename, *blackname, *why, *endtoken;
3109                 ChessMove endtype = (ChessMove) 0;
3110
3111                 if (tkind == 0) {
3112                   whitename = star_match[1];
3113                   blackname = star_match[2];
3114                   why = star_match[3];
3115                   endtoken = star_match[4];
3116                 } else {
3117                   whitename = star_match[1];
3118                   blackname = star_match[3];
3119                   why = star_match[5];
3120                   endtoken = star_match[6];
3121                 }
3122
3123                 /* Game start messages */
3124                 if (strncmp(why, "Creating ", 9) == 0 ||
3125                     strncmp(why, "Continuing ", 11) == 0) {
3126                     gs_gamenum = gamenum;
3127                     strcpy(gs_kind, strchr(why, ' ') + 1);
3128 #if ZIPPY
3129                     if (appData.zippyPlay) {
3130                         ZippyGameStart(whitename, blackname);
3131                     }
3132 #endif /*ZIPPY*/
3133                     continue;
3134                 }
3135
3136                 /* Game end messages */
3137                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3138                     ics_gamenum != gamenum) {
3139                     continue;
3140                 }
3141                 while (endtoken[0] == ' ') endtoken++;
3142                 switch (endtoken[0]) {
3143                   case '*':
3144                   default:
3145                     endtype = GameUnfinished;
3146                     break;
3147                   case '0':
3148                     endtype = BlackWins;
3149                     break;
3150                   case '1':
3151                     if (endtoken[1] == '/')
3152                       endtype = GameIsDrawn;
3153                     else
3154                       endtype = WhiteWins;
3155                     break;
3156                 }
3157                 GameEnds(endtype, why, GE_ICS);
3158 #if ZIPPY
3159                 if (appData.zippyPlay && first.initDone) {
3160                     ZippyGameEnd(endtype, why);
3161                     if (first.pr == NULL) {
3162                       /* Start the next process early so that we'll
3163                          be ready for the next challenge */
3164                       StartChessProgram(&first);
3165                     }
3166                     /* Send "new" early, in case this command takes
3167                        a long time to finish, so that we'll be ready
3168                        for the next challenge. */
3169                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3170                     Reset(TRUE, TRUE);
3171                 }
3172 #endif /*ZIPPY*/
3173                 continue;
3174             }
3175
3176             if (looking_at(buf, &i, "Removing game * from observation") ||
3177                 looking_at(buf, &i, "no longer observing game *") ||
3178                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3179                 if (gameMode == IcsObserving &&
3180                     atoi(star_match[0]) == ics_gamenum)
3181                   {
3182                       /* icsEngineAnalyze */
3183                       if (appData.icsEngineAnalyze) {
3184                             ExitAnalyzeMode();
3185                             ModeHighlight();
3186                       }
3187                       StopClocks();
3188                       gameMode = IcsIdle;
3189                       ics_gamenum = -1;
3190                       ics_user_moved = FALSE;
3191                   }
3192                 continue;
3193             }
3194
3195             if (looking_at(buf, &i, "no longer examining game *")) {
3196                 if (gameMode == IcsExamining &&
3197                     atoi(star_match[0]) == ics_gamenum)
3198                   {
3199                       gameMode = IcsIdle;
3200                       ics_gamenum = -1;
3201                       ics_user_moved = FALSE;
3202                   }
3203                 continue;
3204             }
3205
3206             /* Advance leftover_start past any newlines we find,
3207                so only partial lines can get reparsed */
3208             if (looking_at(buf, &i, "\n")) {
3209                 prevColor = curColor;
3210                 if (curColor != ColorNormal) {
3211                     if (oldi > next_out) {
3212                         SendToPlayer(&buf[next_out], oldi - next_out);
3213                         next_out = oldi;
3214                     }
3215                     Colorize(ColorNormal, FALSE);
3216                     curColor = ColorNormal;
3217                 }
3218                 if (started == STARTED_BOARD) {
3219                     started = STARTED_NONE;
3220                     parse[parse_pos] = NULLCHAR;
3221                     ParseBoard12(parse);
3222                     ics_user_moved = 0;
3223
3224                     /* Send premove here */
3225                     if (appData.premove) {
3226                       char str[MSG_SIZ];
3227                       if (currentMove == 0 &&
3228                           gameMode == IcsPlayingWhite &&
3229                           appData.premoveWhite) {
3230                         sprintf(str, "%s%s\n", ics_prefix,
3231                                 appData.premoveWhiteText);
3232                         if (appData.debugMode)
3233                           fprintf(debugFP, "Sending premove:\n");
3234                         SendToICS(str);
3235                       } else if (currentMove == 1 &&
3236                                  gameMode == IcsPlayingBlack &&
3237                                  appData.premoveBlack) {
3238                         sprintf(str, "%s%s\n", ics_prefix,
3239                                 appData.premoveBlackText);
3240                         if (appData.debugMode)
3241                           fprintf(debugFP, "Sending premove:\n");
3242                         SendToICS(str);
3243                       } else if (gotPremove) {
3244                         gotPremove = 0;
3245                         ClearPremoveHighlights();
3246                         if (appData.debugMode)
3247                           fprintf(debugFP, "Sending premove:\n");
3248                           UserMoveEvent(premoveFromX, premoveFromY, 
3249                                         premoveToX, premoveToY, 
3250                                         premovePromoChar);
3251                       }
3252                     }
3253
3254                     /* Usually suppress following prompt */
3255                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3256                         if (looking_at(buf, &i, "*% ")) {
3257                             savingComment = FALSE;
3258                         }
3259                     }
3260                     next_out = i;
3261                 } else if (started == STARTED_HOLDINGS) {
3262                     int gamenum;
3263                     char new_piece[MSG_SIZ];
3264                     started = STARTED_NONE;
3265                     parse[parse_pos] = NULLCHAR;
3266                     if (appData.debugMode)
3267                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3268                                                         parse, currentMove);
3269                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3270                         gamenum == ics_gamenum) {
3271                         if (gameInfo.variant == VariantNormal) {
3272                           /* [HGM] We seem to switch variant during a game!
3273                            * Presumably no holdings were displayed, so we have
3274                            * to move the position two files to the right to
3275                            * create room for them!
3276                            */
3277                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3278                           /* Get a move list just to see the header, which
3279                              will tell us whether this is really bug or zh */
3280                           if (ics_getting_history == H_FALSE) {
3281                             ics_getting_history = H_REQUESTED;
3282                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3283                             SendToICS(str);
3284                           }
3285                         }
3286                         new_piece[0] = NULLCHAR;
3287                         sscanf(parse, "game %d white [%s black [%s <- %s",
3288                                &gamenum, white_holding, black_holding,
3289                                new_piece);
3290                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3291                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3292                         /* [HGM] copy holdings to board holdings area */
3293                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3294                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3295 #if ZIPPY
3296                         if (appData.zippyPlay && first.initDone) {
3297                             ZippyHoldings(white_holding, black_holding,
3298                                           new_piece);
3299                         }
3300 #endif /*ZIPPY*/
3301                         if (tinyLayout || smallLayout) {
3302                             char wh[16], bh[16];
3303                             PackHolding(wh, white_holding);
3304                             PackHolding(bh, black_holding);
3305                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3306                                     gameInfo.white, gameInfo.black);
3307                         } else {
3308                             sprintf(str, "%s [%s] vs. %s [%s]",
3309                                     gameInfo.white, white_holding,
3310                                     gameInfo.black, black_holding);
3311                         }
3312
3313                         DrawPosition(FALSE, boards[currentMove]);
3314                         DisplayTitle(str);
3315                     }
3316                     /* Suppress following prompt */
3317                     if (looking_at(buf, &i, "*% ")) {
3318                         savingComment = FALSE;
3319                     }
3320                     next_out = i;
3321                 }
3322                 continue;
3323             }
3324
3325             i++;                /* skip unparsed character and loop back */
3326         }
3327         
3328         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3329             started != STARTED_HOLDINGS && i > next_out) {
3330             SendToPlayer(&buf[next_out], i - next_out);
3331             next_out = i;
3332         }
3333         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3334         
3335         leftover_len = buf_len - leftover_start;
3336         /* if buffer ends with something we couldn't parse,
3337            reparse it after appending the next read */
3338         
3339     } else if (count == 0) {
3340         RemoveInputSource(isr);
3341         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3342     } else {
3343         DisplayFatalError(_("Error reading from ICS"), error, 1);
3344     }
3345 }
3346
3347
3348 /* Board style 12 looks like this:
3349    
3350    <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
3351    
3352  * The "<12> " is stripped before it gets to this routine.  The two
3353  * trailing 0's (flip state and clock ticking) are later addition, and
3354  * some chess servers may not have them, or may have only the first.
3355  * Additional trailing fields may be added in the future.  
3356  */
3357
3358 #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"
3359
3360 #define RELATION_OBSERVING_PLAYED    0
3361 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3362 #define RELATION_PLAYING_MYMOVE      1
3363 #define RELATION_PLAYING_NOTMYMOVE  -1
3364 #define RELATION_EXAMINING           2
3365 #define RELATION_ISOLATED_BOARD     -3
3366 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3367
3368 void
3369 ParseBoard12(string)
3370      char *string;
3371
3372     GameMode newGameMode;
3373     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3374     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3375     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3376     char to_play, board_chars[200];
3377     char move_str[500], str[500], elapsed_time[500];
3378     char black[32], white[32];
3379     Board board;
3380     int prevMove = currentMove;
3381     int ticking = 2;
3382     ChessMove moveType;
3383     int fromX, fromY, toX, toY;
3384     char promoChar;
3385     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3386     char *bookHit = NULL; // [HGM] book
3387
3388     fromX = fromY = toX = toY = -1;
3389     
3390     newGame = FALSE;
3391
3392     if (appData.debugMode)
3393       fprintf(debugFP, _("Parsing board: %s\n"), string);
3394
3395     move_str[0] = NULLCHAR;
3396     elapsed_time[0] = NULLCHAR;
3397     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3398         int  i = 0, j;
3399         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3400             if(string[i] == ' ') { ranks++; files = 0; }
3401             else files++;
3402             i++;
3403         }
3404         for(j = 0; j <i; j++) board_chars[j] = string[j];
3405         board_chars[i] = '\0';
3406         string += i + 1;
3407     }
3408     n = sscanf(string, PATTERN, &to_play, &double_push,
3409                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3410                &gamenum, white, black, &relation, &basetime, &increment,
3411                &white_stren, &black_stren, &white_time, &black_time,
3412                &moveNum, str, elapsed_time, move_str, &ics_flip,
3413                &ticking);
3414
3415     if (n < 21) {
3416         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3417         DisplayError(str, 0);
3418         return;
3419     }
3420
3421     /* Convert the move number to internal form */
3422     moveNum = (moveNum - 1) * 2;
3423     if (to_play == 'B') moveNum++;
3424     if (moveNum >= MAX_MOVES) {
3425       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3426                         0, 1);
3427       return;
3428     }
3429     
3430     switch (relation) {
3431       case RELATION_OBSERVING_PLAYED:
3432       case RELATION_OBSERVING_STATIC:
3433         if (gamenum == -1) {
3434             /* Old ICC buglet */
3435             relation = RELATION_OBSERVING_STATIC;
3436         }
3437         newGameMode = IcsObserving;
3438         break;
3439       case RELATION_PLAYING_MYMOVE:
3440       case RELATION_PLAYING_NOTMYMOVE:
3441         newGameMode =
3442           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3443             IcsPlayingWhite : IcsPlayingBlack;
3444         break;
3445       case RELATION_EXAMINING:
3446         newGameMode = IcsExamining;
3447         break;
3448       case RELATION_ISOLATED_BOARD:
3449       default:
3450         /* Just display this board.  If user was doing something else,
3451            we will forget about it until the next board comes. */ 
3452         newGameMode = IcsIdle;
3453         break;
3454       case RELATION_STARTING_POSITION:
3455         newGameMode = gameMode;
3456         break;
3457     }
3458     
3459     /* Modify behavior for initial board display on move listing
3460        of wild games.
3461        */
3462     switch (ics_getting_history) {
3463       case H_FALSE:
3464       case H_REQUESTED:
3465         break;
3466       case H_GOT_REQ_HEADER:
3467       case H_GOT_UNREQ_HEADER:
3468         /* This is the initial position of the current game */
3469         gamenum = ics_gamenum;
3470         moveNum = 0;            /* old ICS bug workaround */
3471         if (to_play == 'B') {
3472           startedFromSetupPosition = TRUE;
3473           blackPlaysFirst = TRUE;
3474           moveNum = 1;
3475           if (forwardMostMove == 0) forwardMostMove = 1;
3476           if (backwardMostMove == 0) backwardMostMove = 1;
3477           if (currentMove == 0) currentMove = 1;
3478         }
3479         newGameMode = gameMode;
3480         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3481         break;
3482       case H_GOT_UNWANTED_HEADER:
3483         /* This is an initial board that we don't want */
3484         return;
3485       case H_GETTING_MOVES:
3486         /* Should not happen */
3487         DisplayError(_("Error gathering move list: extra board"), 0);
3488         ics_getting_history = H_FALSE;
3489         return;
3490     }
3491     
3492     /* Take action if this is the first board of a new game, or of a
3493        different game than is currently being displayed.  */
3494     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3495         relation == RELATION_ISOLATED_BOARD) {
3496         
3497         /* Forget the old game and get the history (if any) of the new one */
3498         if (gameMode != BeginningOfGame) {
3499           Reset(FALSE, TRUE);
3500         }
3501         newGame = TRUE;
3502         if (appData.autoRaiseBoard) BoardToTop();
3503         prevMove = -3;
3504         if (gamenum == -1) {
3505             newGameMode = IcsIdle;
3506         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3507                    appData.getMoveList) {
3508             /* Need to get game history */
3509             ics_getting_history = H_REQUESTED;
3510             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3511             SendToICS(str);
3512         }
3513         
3514         /* Initially flip the board to have black on the bottom if playing
3515            black or if the ICS flip flag is set, but let the user change
3516            it with the Flip View button. */
3517         flipView = appData.autoFlipView ? 
3518           (newGameMode == IcsPlayingBlack) || ics_flip :
3519           appData.flipView;
3520         
3521         /* Done with values from previous mode; copy in new ones */
3522         gameMode = newGameMode;
3523         ModeHighlight();
3524         ics_gamenum = gamenum;
3525         if (gamenum == gs_gamenum) {
3526             int klen = strlen(gs_kind);
3527             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3528             sprintf(str, "ICS %s", gs_kind);
3529             gameInfo.event = StrSave(str);
3530         } else {
3531             gameInfo.event = StrSave("ICS game");
3532         }
3533         gameInfo.site = StrSave(appData.icsHost);
3534         gameInfo.date = PGNDate();
3535         gameInfo.round = StrSave("-");
3536         gameInfo.white = StrSave(white);
3537         gameInfo.black = StrSave(black);
3538         timeControl = basetime * 60 * 1000;
3539         timeControl_2 = 0;
3540         timeIncrement = increment * 1000;
3541         movesPerSession = 0;
3542         gameInfo.timeControl = TimeControlTagValue();
3543         VariantSwitch(board, StringToVariant(gameInfo.event) );
3544   if (appData.debugMode) {
3545     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3546     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3547     setbuf(debugFP, NULL);
3548   }
3549
3550         gameInfo.outOfBook = NULL;
3551         
3552         /* Do we have the ratings? */
3553         if (strcmp(player1Name, white) == 0 &&
3554             strcmp(player2Name, black) == 0) {
3555             if (appData.debugMode)
3556               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3557                       player1Rating, player2Rating);
3558             gameInfo.whiteRating = player1Rating;
3559             gameInfo.blackRating = player2Rating;
3560         } else if (strcmp(player2Name, white) == 0 &&
3561                    strcmp(player1Name, black) == 0) {
3562             if (appData.debugMode)
3563               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3564                       player2Rating, player1Rating);
3565             gameInfo.whiteRating = player2Rating;
3566             gameInfo.blackRating = player1Rating;
3567         }
3568         player1Name[0] = player2Name[0] = NULLCHAR;
3569
3570         /* Silence shouts if requested */
3571         if (appData.quietPlay &&
3572             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3573             SendToICS(ics_prefix);
3574             SendToICS("set shout 0\n");
3575         }
3576     }
3577     
3578     /* Deal with midgame name changes */
3579     if (!newGame) {
3580         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3581             if (gameInfo.white) free(gameInfo.white);
3582             gameInfo.white = StrSave(white);
3583         }
3584         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3585             if (gameInfo.black) free(gameInfo.black);
3586             gameInfo.black = StrSave(black);
3587         }
3588     }
3589     
3590     /* Throw away game result if anything actually changes in examine mode */
3591     if (gameMode == IcsExamining && !newGame) {
3592         gameInfo.result = GameUnfinished;
3593         if (gameInfo.resultDetails != NULL) {
3594             free(gameInfo.resultDetails);
3595             gameInfo.resultDetails = NULL;
3596         }
3597     }
3598     
3599     /* In pausing && IcsExamining mode, we ignore boards coming
3600        in if they are in a different variation than we are. */
3601     if (pauseExamInvalid) return;
3602     if (pausing && gameMode == IcsExamining) {
3603         if (moveNum <= pauseExamForwardMostMove) {
3604             pauseExamInvalid = TRUE;
3605             forwardMostMove = pauseExamForwardMostMove;
3606             return;
3607         }
3608     }
3609     
3610   if (appData.debugMode) {
3611     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3612   }
3613     /* Parse the board */
3614     for (k = 0; k < ranks; k++) {
3615       for (j = 0; j < files; j++)
3616         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3617       if(gameInfo.holdingsWidth > 1) {
3618            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3619            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3620       }
3621     }
3622     CopyBoard(boards[moveNum], board);
3623     if (moveNum == 0) {
3624         startedFromSetupPosition =
3625           !CompareBoards(board, initialPosition);
3626         if(startedFromSetupPosition)
3627             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3628     }
3629
3630     /* [HGM] Set castling rights. Take the outermost Rooks,
3631        to make it also work for FRC opening positions. Note that board12
3632        is really defective for later FRC positions, as it has no way to
3633        indicate which Rook can castle if they are on the same side of King.
3634        For the initial position we grant rights to the outermost Rooks,
3635        and remember thos rights, and we then copy them on positions
3636        later in an FRC game. This means WB might not recognize castlings with
3637        Rooks that have moved back to their original position as illegal,
3638        but in ICS mode that is not its job anyway.
3639     */
3640     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3641     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3642
3643         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3644             if(board[0][i] == WhiteRook) j = i;
3645         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3646         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3647             if(board[0][i] == WhiteRook) j = i;
3648         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3649         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3650             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3651         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3652         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3653             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3654         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3655
3656         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3657         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3658             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3659         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3660             if(board[BOARD_HEIGHT-1][k] == bKing)
3661                 initialRights[5] = castlingRights[moveNum][5] = k;
3662     } else { int r;
3663         r = castlingRights[moveNum][0] = initialRights[0];
3664         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3665         r = castlingRights[moveNum][1] = initialRights[1];
3666         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3667         r = castlingRights[moveNum][3] = initialRights[3];
3668         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3669         r = castlingRights[moveNum][4] = initialRights[4];
3670         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3671         /* wildcastle kludge: always assume King has rights */
3672         r = castlingRights[moveNum][2] = initialRights[2];
3673         r = castlingRights[moveNum][5] = initialRights[5];
3674     }
3675     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3676     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3677
3678     
3679     if (ics_getting_history == H_GOT_REQ_HEADER ||
3680         ics_getting_history == H_GOT_UNREQ_HEADER) {
3681         /* This was an initial position from a move list, not
3682            the current position */
3683         return;
3684     }
3685     
3686     /* Update currentMove and known move number limits */
3687     newMove = newGame || moveNum > forwardMostMove;
3688
3689     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3690     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3691         takeback = forwardMostMove - moveNum;
3692         for (i = 0; i < takeback; i++) {
3693              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3694              SendToProgram("undo\n", &first);
3695         }
3696     }
3697
3698     if (newGame) {
3699         forwardMostMove = backwardMostMove = currentMove = moveNum;
3700         if (gameMode == IcsExamining && moveNum == 0) {
3701           /* Workaround for ICS limitation: we are not told the wild
3702              type when starting to examine a game.  But if we ask for
3703              the move list, the move list header will tell us */
3704             ics_getting_history = H_REQUESTED;
3705             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3706             SendToICS(str);
3707         }
3708     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3709                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3710         forwardMostMove = moveNum;
3711         if (!pausing || currentMove > forwardMostMove)
3712           currentMove = forwardMostMove;
3713     } else {
3714         /* New part of history that is not contiguous with old part */ 
3715         if (pausing && gameMode == IcsExamining) {
3716             pauseExamInvalid = TRUE;
3717             forwardMostMove = pauseExamForwardMostMove;
3718             return;
3719         }
3720         forwardMostMove = backwardMostMove = currentMove = moveNum;
3721         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3722             ics_getting_history = H_REQUESTED;
3723             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3724             SendToICS(str);
3725         }
3726     }
3727     
3728     /* Update the clocks */
3729     if (strchr(elapsed_time, '.')) {
3730       /* Time is in ms */
3731       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3732       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3733     } else {
3734       /* Time is in seconds */
3735       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3736       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3737     }
3738       
3739
3740 #if ZIPPY
3741     if (appData.zippyPlay && newGame &&
3742         gameMode != IcsObserving && gameMode != IcsIdle &&
3743         gameMode != IcsExamining)
3744       ZippyFirstBoard(moveNum, basetime, increment);
3745 #endif
3746     
3747     /* Put the move on the move list, first converting
3748        to canonical algebraic form. */
3749     if (moveNum > 0) {
3750   if (appData.debugMode) {
3751     if (appData.debugMode) { int f = forwardMostMove;
3752         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3753                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3754     }
3755     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3756     fprintf(debugFP, "moveNum = %d\n", moveNum);
3757     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3758     setbuf(debugFP, NULL);
3759   }
3760         if (moveNum <= backwardMostMove) {
3761             /* We don't know what the board looked like before
3762                this move.  Punt. */
3763             strcpy(parseList[moveNum - 1], move_str);
3764             strcat(parseList[moveNum - 1], " ");
3765             strcat(parseList[moveNum - 1], elapsed_time);
3766             moveList[moveNum - 1][0] = NULLCHAR;
3767         } else if (strcmp(move_str, "none") == 0) {
3768             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3769             /* Again, we don't know what the board looked like;
3770                this is really the start of the game. */
3771             parseList[moveNum - 1][0] = NULLCHAR;
3772             moveList[moveNum - 1][0] = NULLCHAR;
3773             backwardMostMove = moveNum;
3774             startedFromSetupPosition = TRUE;
3775             fromX = fromY = toX = toY = -1;
3776         } else {
3777           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3778           //                 So we parse the long-algebraic move string in stead of the SAN move
3779           int valid; char buf[MSG_SIZ], *prom;
3780
3781           // str looks something like "Q/a1-a2"; kill the slash
3782           if(str[1] == '/') 
3783                 sprintf(buf, "%c%s", str[0], str+2);
3784           else  strcpy(buf, str); // might be castling
3785           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3786                 strcat(buf, prom); // long move lacks promo specification!
3787           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3788                 if(appData.debugMode) 
3789                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3790                 strcpy(move_str, buf);
3791           }
3792           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3793                                 &fromX, &fromY, &toX, &toY, &promoChar)
3794                || ParseOneMove(buf, moveNum - 1, &moveType,
3795                                 &fromX, &fromY, &toX, &toY, &promoChar);
3796           // end of long SAN patch
3797           if (valid) {
3798             (void) CoordsToAlgebraic(boards[moveNum - 1],
3799                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3800                                      fromY, fromX, toY, toX, promoChar,
3801                                      parseList[moveNum-1]);
3802             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3803                              castlingRights[moveNum]) ) {
3804               case MT_NONE:
3805               case MT_STALEMATE:
3806               default:
3807                 break;
3808               case MT_CHECK:
3809                 if(gameInfo.variant != VariantShogi)
3810                     strcat(parseList[moveNum - 1], "+");
3811                 break;
3812               case MT_CHECKMATE:
3813               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3814                 strcat(parseList[moveNum - 1], "#");
3815                 break;
3816             }
3817             strcat(parseList[moveNum - 1], " ");
3818             strcat(parseList[moveNum - 1], elapsed_time);
3819             /* currentMoveString is set as a side-effect of ParseOneMove */
3820             strcpy(moveList[moveNum - 1], currentMoveString);
3821             strcat(moveList[moveNum - 1], "\n");
3822           } else {
3823             /* Move from ICS was illegal!?  Punt. */
3824   if (appData.debugMode) {
3825     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3826     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3827   }
3828 #if 0
3829             if (appData.testLegality && appData.debugMode) {
3830                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3831                 DisplayError(str, 0);
3832             }
3833 #endif
3834             strcpy(parseList[moveNum - 1], move_str);
3835             strcat(parseList[moveNum - 1], " ");
3836             strcat(parseList[moveNum - 1], elapsed_time);
3837             moveList[moveNum - 1][0] = NULLCHAR;
3838             fromX = fromY = toX = toY = -1;
3839           }
3840         }
3841   if (appData.debugMode) {
3842     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3843     setbuf(debugFP, NULL);
3844   }
3845
3846 #if ZIPPY
3847         /* Send move to chess program (BEFORE animating it). */
3848         if (appData.zippyPlay && !newGame && newMove && 
3849            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3850
3851             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3852                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3853                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3854                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3855                             move_str);
3856                     DisplayError(str, 0);
3857                 } else {
3858                     if (first.sendTime) {
3859                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3860                     }
3861                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3862                     if (firstMove && !bookHit) {
3863                         firstMove = FALSE;
3864                         if (first.useColors) {
3865                           SendToProgram(gameMode == IcsPlayingWhite ?
3866                                         "white\ngo\n" :
3867                                         "black\ngo\n", &first);
3868                         } else {
3869                           SendToProgram("go\n", &first);
3870                         }
3871                         first.maybeThinking = TRUE;
3872                     }
3873                 }
3874             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3875               if (moveList[moveNum - 1][0] == NULLCHAR) {
3876                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3877                 DisplayError(str, 0);
3878               } else {
3879                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3880                 SendMoveToProgram(moveNum - 1, &first);
3881               }
3882             }
3883         }
3884 #endif
3885     }
3886
3887     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3888         /* If move comes from a remote source, animate it.  If it
3889            isn't remote, it will have already been animated. */
3890         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3891             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3892         }
3893         if (!pausing && appData.highlightLastMove) {
3894             SetHighlights(fromX, fromY, toX, toY);
3895         }
3896     }
3897     
3898     /* Start the clocks */
3899     whiteFlag = blackFlag = FALSE;
3900     appData.clockMode = !(basetime == 0 && increment == 0);
3901     if (ticking == 0) {
3902       ics_clock_paused = TRUE;
3903       StopClocks();
3904     } else if (ticking == 1) {
3905       ics_clock_paused = FALSE;
3906     }
3907     if (gameMode == IcsIdle ||
3908         relation == RELATION_OBSERVING_STATIC ||
3909         relation == RELATION_EXAMINING ||
3910         ics_clock_paused)
3911       DisplayBothClocks();
3912     else
3913       StartClocks();
3914     
3915     /* Display opponents and material strengths */
3916     if (gameInfo.variant != VariantBughouse &&
3917         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3918         if (tinyLayout || smallLayout) {
3919             if(gameInfo.variant == VariantNormal)
3920                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3921                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3922                     basetime, increment);
3923             else
3924                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3925                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3926                     basetime, increment, (int) gameInfo.variant);
3927         } else {
3928             if(gameInfo.variant == VariantNormal)
3929                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3930                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3931                     basetime, increment);
3932             else
3933                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3934                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3935                     basetime, increment, VariantName(gameInfo.variant));
3936         }
3937         DisplayTitle(str);
3938   if (appData.debugMode) {
3939     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3940   }
3941     }
3942
3943    
3944     /* Display the board */
3945     if (!pausing && !appData.noGUI) {
3946       
3947       if (appData.premove)
3948           if (!gotPremove || 
3949              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3950              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3951               ClearPremoveHighlights();
3952
3953       DrawPosition(FALSE, boards[currentMove]);
3954       DisplayMove(moveNum - 1);
3955       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3956             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3957               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3958         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3959       }
3960     }
3961
3962     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3963 #if ZIPPY
3964     if(bookHit) { // [HGM] book: simulate book reply
3965         static char bookMove[MSG_SIZ]; // a bit generous?
3966
3967         programStats.nodes = programStats.depth = programStats.time = 
3968         programStats.score = programStats.got_only_move = 0;
3969         sprintf(programStats.movelist, "%s (xbook)", bookHit);
3970
3971         strcpy(bookMove, "move ");
3972         strcat(bookMove, bookHit);
3973         HandleMachineMove(bookMove, &first);
3974     }
3975 #endif
3976 }
3977
3978 void
3979 GetMoveListEvent()
3980 {
3981     char buf[MSG_SIZ];
3982     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3983         ics_getting_history = H_REQUESTED;
3984         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3985         SendToICS(buf);
3986     }
3987 }
3988
3989 void
3990 AnalysisPeriodicEvent(force)
3991      int force;
3992 {
3993     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3994          && !force) || !appData.periodicUpdates)
3995       return;
3996
3997     /* Send . command to Crafty to collect stats */
3998     SendToProgram(".\n", &first);
3999
4000     /* Don't send another until we get a response (this makes
4001        us stop sending to old Crafty's which don't understand
4002        the "." command (sending illegal cmds resets node count & time,
4003        which looks bad)) */
4004     programStats.ok_to_send = 0;
4005 }
4006
4007 void
4008 SendMoveToProgram(moveNum, cps)
4009      int moveNum;
4010      ChessProgramState *cps;
4011 {
4012     char buf[MSG_SIZ];
4013
4014     if (cps->useUsermove) {
4015       SendToProgram("usermove ", cps);
4016     }
4017     if (cps->useSAN) {
4018       char *space;
4019       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4020         int len = space - parseList[moveNum];
4021         memcpy(buf, parseList[moveNum], len);
4022         buf[len++] = '\n';
4023         buf[len] = NULLCHAR;
4024       } else {
4025         sprintf(buf, "%s\n", parseList[moveNum]);
4026       }
4027       SendToProgram(buf, cps);
4028     } else {
4029       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4030         AlphaRank(moveList[moveNum], 4);
4031         SendToProgram(moveList[moveNum], cps);
4032         AlphaRank(moveList[moveNum], 4); // and back
4033       } else
4034       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4035        * the engine. It would be nice to have a better way to identify castle 
4036        * moves here. */
4037       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4038                                                                          && cps->useOOCastle) {
4039         int fromX = moveList[moveNum][0] - AAA; 
4040         int fromY = moveList[moveNum][1] - ONE;
4041         int toX = moveList[moveNum][2] - AAA; 
4042         int toY = moveList[moveNum][3] - ONE;
4043         if((boards[moveNum][fromY][fromX] == WhiteKing 
4044             && boards[moveNum][toY][toX] == WhiteRook)
4045            || (boards[moveNum][fromY][fromX] == BlackKing 
4046                && boards[moveNum][toY][toX] == BlackRook)) {
4047           if(toX > fromX) SendToProgram("O-O\n", cps);
4048           else SendToProgram("O-O-O\n", cps);
4049         }
4050         else SendToProgram(moveList[moveNum], cps);
4051       }
4052       else SendToProgram(moveList[moveNum], cps);
4053       /* End of additions by Tord */
4054     }
4055
4056     /* [HGM] setting up the opening has brought engine in force mode! */
4057     /*       Send 'go' if we are in a mode where machine should play. */
4058     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4059         (gameMode == TwoMachinesPlay   ||
4060 #ifdef ZIPPY
4061          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4062 #endif
4063          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4064         SendToProgram("go\n", cps);
4065   if (appData.debugMode) {
4066     fprintf(debugFP, "(extra)\n");
4067   }
4068     }
4069     setboardSpoiledMachineBlack = 0;
4070 }
4071
4072 void
4073 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4074      ChessMove moveType;
4075      int fromX, fromY, toX, toY;
4076 {
4077     char user_move[MSG_SIZ];
4078
4079     switch (moveType) {
4080       default:
4081         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4082                 (int)moveType, fromX, fromY, toX, toY);
4083         DisplayError(user_move + strlen("say "), 0);
4084         break;
4085       case WhiteKingSideCastle:
4086       case BlackKingSideCastle:
4087       case WhiteQueenSideCastleWild:
4088       case BlackQueenSideCastleWild:
4089       /* PUSH Fabien */
4090       case WhiteHSideCastleFR:
4091       case BlackHSideCastleFR:
4092       /* POP Fabien */
4093         sprintf(user_move, "o-o\n");
4094         break;
4095       case WhiteQueenSideCastle:
4096       case BlackQueenSideCastle:
4097       case WhiteKingSideCastleWild:
4098       case BlackKingSideCastleWild:
4099       /* PUSH Fabien */
4100       case WhiteASideCastleFR:
4101       case BlackASideCastleFR:
4102       /* POP Fabien */
4103         sprintf(user_move, "o-o-o\n");
4104         break;
4105       case WhitePromotionQueen:
4106       case BlackPromotionQueen:
4107       case WhitePromotionRook:
4108       case BlackPromotionRook:
4109       case WhitePromotionBishop:
4110       case BlackPromotionBishop:
4111       case WhitePromotionKnight:
4112       case BlackPromotionKnight:
4113       case WhitePromotionKing:
4114       case BlackPromotionKing:
4115       case WhitePromotionChancellor:
4116       case BlackPromotionChancellor:
4117       case WhitePromotionArchbishop:
4118       case BlackPromotionArchbishop:
4119         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4120             sprintf(user_move, "%c%c%c%c=%c\n",
4121                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4122                 PieceToChar(WhiteFerz));
4123         else if(gameInfo.variant == VariantGreat)
4124             sprintf(user_move, "%c%c%c%c=%c\n",
4125                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4126                 PieceToChar(WhiteMan));
4127         else
4128             sprintf(user_move, "%c%c%c%c=%c\n",
4129                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4130                 PieceToChar(PromoPiece(moveType)));
4131         break;
4132       case WhiteDrop:
4133       case BlackDrop:
4134         sprintf(user_move, "%c@%c%c\n",
4135                 ToUpper(PieceToChar((ChessSquare) fromX)),
4136                 AAA + toX, ONE + toY);
4137         break;
4138       case NormalMove:
4139       case WhiteCapturesEnPassant:
4140       case BlackCapturesEnPassant:
4141       case IllegalMove:  /* could be a variant we don't quite understand */
4142         sprintf(user_move, "%c%c%c%c\n",
4143                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4144         break;
4145     }
4146     SendToICS(user_move);
4147     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4148         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4149 }
4150
4151 void
4152 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4153      int rf, ff, rt, ft;
4154      char promoChar;
4155      char move[7];
4156 {
4157     if (rf == DROP_RANK) {
4158         sprintf(move, "%c@%c%c\n",
4159                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4160     } else {
4161         if (promoChar == 'x' || promoChar == NULLCHAR) {
4162             sprintf(move, "%c%c%c%c\n",
4163                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4164         } else {
4165             sprintf(move, "%c%c%c%c%c\n",
4166                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4167         }
4168     }
4169 }
4170
4171 void
4172 ProcessICSInitScript(f)
4173      FILE *f;
4174 {
4175     char buf[MSG_SIZ];
4176
4177     while (fgets(buf, MSG_SIZ, f)) {
4178         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4179     }
4180
4181     fclose(f);
4182 }
4183
4184
4185 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4186 void
4187 AlphaRank(char *move, int n)
4188 {
4189 //    char *p = move, c; int x, y;
4190
4191     if (appData.debugMode) {
4192         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4193     }
4194
4195     if(move[1]=='*' && 
4196        move[2]>='0' && move[2]<='9' &&
4197        move[3]>='a' && move[3]<='x'    ) {
4198         move[1] = '@';
4199         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4200         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4201     } else
4202     if(move[0]>='0' && move[0]<='9' &&
4203        move[1]>='a' && move[1]<='x' &&
4204        move[2]>='0' && move[2]<='9' &&
4205        move[3]>='a' && move[3]<='x'    ) {
4206         /* input move, Shogi -> normal */
4207         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4208         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4209         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4210         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4211     } else
4212     if(move[1]=='@' &&
4213        move[3]>='0' && move[3]<='9' &&
4214        move[2]>='a' && move[2]<='x'    ) {
4215         move[1] = '*';
4216         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4217         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4218     } else
4219     if(
4220        move[0]>='a' && move[0]<='x' &&
4221        move[3]>='0' && move[3]<='9' &&
4222        move[2]>='a' && move[2]<='x'    ) {
4223          /* output move, normal -> Shogi */
4224         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4225         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4226         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4227         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4228         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4229     }
4230     if (appData.debugMode) {
4231         fprintf(debugFP, "   out = '%s'\n", move);
4232     }
4233 }
4234
4235 /* Parser for moves from gnuchess, ICS, or user typein box */
4236 Boolean
4237 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4238      char *move;
4239      int moveNum;
4240      ChessMove *moveType;
4241      int *fromX, *fromY, *toX, *toY;
4242      char *promoChar;
4243 {       
4244     if (appData.debugMode) {
4245         fprintf(debugFP, "move to parse: %s\n", move);
4246     }
4247     *moveType = yylexstr(moveNum, move);
4248
4249     switch (*moveType) {
4250       case WhitePromotionChancellor:
4251       case BlackPromotionChancellor:
4252       case WhitePromotionArchbishop:
4253       case BlackPromotionArchbishop:
4254       case WhitePromotionQueen:
4255       case BlackPromotionQueen:
4256       case WhitePromotionRook:
4257       case BlackPromotionRook:
4258       case WhitePromotionBishop:
4259       case BlackPromotionBishop:
4260       case WhitePromotionKnight:
4261       case BlackPromotionKnight:
4262       case WhitePromotionKing:
4263       case BlackPromotionKing:
4264       case NormalMove:
4265       case WhiteCapturesEnPassant:
4266       case BlackCapturesEnPassant:
4267       case WhiteKingSideCastle:
4268       case WhiteQueenSideCastle:
4269       case BlackKingSideCastle:
4270       case BlackQueenSideCastle:
4271       case WhiteKingSideCastleWild:
4272       case WhiteQueenSideCastleWild:
4273       case BlackKingSideCastleWild:
4274       case BlackQueenSideCastleWild:
4275       /* Code added by Tord: */
4276       case WhiteHSideCastleFR:
4277       case WhiteASideCastleFR:
4278       case BlackHSideCastleFR:
4279       case BlackASideCastleFR:
4280       /* End of code added by Tord */
4281       case IllegalMove:         /* bug or odd chess variant */
4282         *fromX = currentMoveString[0] - AAA;
4283         *fromY = currentMoveString[1] - ONE;
4284         *toX = currentMoveString[2] - AAA;
4285         *toY = currentMoveString[3] - ONE;
4286         *promoChar = currentMoveString[4];
4287         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4288             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4289     if (appData.debugMode) {
4290         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4291     }
4292             *fromX = *fromY = *toX = *toY = 0;
4293             return FALSE;
4294         }
4295         if (appData.testLegality) {
4296           return (*moveType != IllegalMove);
4297         } else {
4298           return !(fromX == fromY && toX == toY);
4299         }
4300
4301       case WhiteDrop:
4302       case BlackDrop:
4303         *fromX = *moveType == WhiteDrop ?
4304           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4305           (int) CharToPiece(ToLower(currentMoveString[0]));
4306         *fromY = DROP_RANK;
4307         *toX = currentMoveString[2] - AAA;
4308         *toY = currentMoveString[3] - ONE;
4309         *promoChar = NULLCHAR;
4310         return TRUE;
4311
4312       case AmbiguousMove:
4313       case ImpossibleMove:
4314       case (ChessMove) 0:       /* end of file */
4315       case ElapsedTime:
4316       case Comment:
4317       case PGNTag:
4318       case NAG:
4319       case WhiteWins:
4320       case BlackWins:
4321       case GameIsDrawn:
4322       default:
4323     if (appData.debugMode) {
4324         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4325     }
4326         /* bug? */
4327         *fromX = *fromY = *toX = *toY = 0;
4328         *promoChar = NULLCHAR;
4329         return FALSE;
4330     }
4331 }
4332
4333 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4334 // All positions will have equal probability, but the current method will not provide a unique
4335 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4336 #define DARK 1
4337 #define LITE 2
4338 #define ANY 3
4339
4340 int squaresLeft[4];
4341 int piecesLeft[(int)BlackPawn];
4342 int seed, nrOfShuffles;
4343
4344 void GetPositionNumber()
4345 {       // sets global variable seed
4346         int i;
4347
4348         seed = appData.defaultFrcPosition;
4349         if(seed < 0) { // randomize based on time for negative FRC position numbers
4350                 for(i=0; i<50; i++) seed += random();
4351                 seed = random() ^ random() >> 8 ^ random() << 8;
4352                 if(seed<0) seed = -seed;
4353         }
4354 }
4355
4356 int put(Board board, int pieceType, int rank, int n, int shade)
4357 // put the piece on the (n-1)-th empty squares of the given shade
4358 {
4359         int i;
4360
4361         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4362                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4363                         board[rank][i] = (ChessSquare) pieceType;
4364                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4365                         squaresLeft[ANY]--;
4366                         piecesLeft[pieceType]--; 
4367                         return i;
4368                 }
4369         }
4370         return -1;
4371 }
4372
4373
4374 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4375 // calculate where the next piece goes, (any empty square), and put it there
4376 {
4377         int i;
4378
4379         i = seed % squaresLeft[shade];
4380         nrOfShuffles *= squaresLeft[shade];
4381         seed /= squaresLeft[shade];
4382         put(board, pieceType, rank, i, shade);
4383 }
4384
4385 void AddTwoPieces(Board board, int pieceType, int rank)
4386 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4387 {
4388         int i, n=squaresLeft[ANY], j=n-1, k;
4389
4390         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4391         i = seed % k;  // pick one
4392         nrOfShuffles *= k;
4393         seed /= k;
4394         while(i >= j) i -= j--;
4395         j = n - 1 - j; i += j;
4396         put(board, pieceType, rank, j, ANY);
4397         put(board, pieceType, rank, i, ANY);
4398 }
4399
4400 void SetUpShuffle(Board board, int number)
4401 {
4402         int i, p, first=1;
4403
4404         GetPositionNumber(); nrOfShuffles = 1;
4405
4406         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4407         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4408         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4409
4410         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4411
4412         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4413             p = (int) board[0][i];
4414             if(p < (int) BlackPawn) piecesLeft[p] ++;
4415             board[0][i] = EmptySquare;
4416         }
4417
4418         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4419             // shuffles restricted to allow normal castling put KRR first
4420             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4421                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4422             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4423                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4424             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4425                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4426             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4427                 put(board, WhiteRook, 0, 0, ANY);
4428             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4429         }
4430
4431         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4432             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4433             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4434                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4435                 while(piecesLeft[p] >= 2) {
4436                     AddOnePiece(board, p, 0, LITE);
4437                     AddOnePiece(board, p, 0, DARK);
4438                 }
4439                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4440             }
4441
4442         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4443             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4444             // but we leave King and Rooks for last, to possibly obey FRC restriction
4445             if(p == (int)WhiteRook) continue;
4446             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4447             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4448         }
4449
4450         // now everything is placed, except perhaps King (Unicorn) and Rooks
4451
4452         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4453             // Last King gets castling rights
4454             while(piecesLeft[(int)WhiteUnicorn]) {
4455                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4456                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4457             }
4458
4459             while(piecesLeft[(int)WhiteKing]) {
4460                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4461                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4462             }
4463
4464
4465         } else {
4466             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4467             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4468         }
4469
4470         // Only Rooks can be left; simply place them all
4471         while(piecesLeft[(int)WhiteRook]) {
4472                 i = put(board, WhiteRook, 0, 0, ANY);
4473                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4474                         if(first) {
4475                                 first=0;
4476                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4477                         }
4478                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4479                 }
4480         }
4481         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4482             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4483         }
4484
4485         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4486 }
4487
4488 int SetCharTable( char *table, const char * map )
4489 /* [HGM] moved here from winboard.c because of its general usefulness */
4490 /*       Basically a safe strcpy that uses the last character as King */
4491 {
4492     int result = FALSE; int NrPieces;
4493
4494     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4495                     && NrPieces >= 12 && !(NrPieces&1)) {
4496         int i; /* [HGM] Accept even length from 12 to 34 */
4497
4498         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4499         for( i=0; i<NrPieces/2-1; i++ ) {
4500             table[i] = map[i];
4501             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4502         }
4503         table[(int) WhiteKing]  = map[NrPieces/2-1];
4504         table[(int) BlackKing]  = map[NrPieces-1];
4505
4506         result = TRUE;
4507     }
4508
4509     return result;
4510 }
4511
4512 void Prelude(Board board)
4513 {       // [HGM] superchess: random selection of exo-pieces
4514         int i, j, k; ChessSquare p; 
4515         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4516
4517         GetPositionNumber(); // use FRC position number
4518
4519         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4520             SetCharTable(pieceToChar, appData.pieceToCharTable);
4521             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4522                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4523         }
4524
4525         j = seed%4;                 seed /= 4; 
4526         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4527         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4528         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4529         j = seed%3 + (seed%3 >= j); seed /= 3; 
4530         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4531         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4532         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4533         j = seed%3;                 seed /= 3; 
4534         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4535         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4536         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4537         j = seed%2 + (seed%2 >= j); seed /= 2; 
4538         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4539         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4540         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4541         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4542         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4543         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4544         put(board, exoPieces[0],    0, 0, ANY);
4545         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4546 }
4547
4548 void
4549 InitPosition(redraw)
4550      int redraw;
4551 {
4552     ChessSquare (* pieces)[BOARD_SIZE];
4553     int i, j, pawnRow, overrule,
4554     oldx = gameInfo.boardWidth,
4555     oldy = gameInfo.boardHeight,
4556     oldh = gameInfo.holdingsWidth,
4557     oldv = gameInfo.variant;
4558
4559     currentMove = forwardMostMove = backwardMostMove = 0;
4560     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4561
4562     /* [AS] Initialize pv info list [HGM] and game status */
4563     {
4564         for( i=0; i<MAX_MOVES; i++ ) {
4565             pvInfoList[i].depth = 0;
4566             epStatus[i]=EP_NONE;
4567             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4568         }
4569
4570         initialRulePlies = 0; /* 50-move counter start */
4571
4572         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4573         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4574     }
4575
4576     
4577     /* [HGM] logic here is completely changed. In stead of full positions */
4578     /* the initialized data only consist of the two backranks. The switch */
4579     /* selects which one we will use, which is than copied to the Board   */
4580     /* initialPosition, which for the rest is initialized by Pawns and    */
4581     /* empty squares. This initial position is then copied to boards[0],  */
4582     /* possibly after shuffling, so that it remains available.            */
4583
4584     gameInfo.holdingsWidth = 0; /* default board sizes */
4585     gameInfo.boardWidth    = 8;
4586     gameInfo.boardHeight   = 8;
4587     gameInfo.holdingsSize  = 0;
4588     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4589     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4590     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4591
4592     switch (gameInfo.variant) {
4593     case VariantFischeRandom:
4594       shuffleOpenings = TRUE;
4595     default:
4596       pieces = FIDEArray;
4597       break;
4598     case VariantShatranj:
4599       pieces = ShatranjArray;
4600       nrCastlingRights = 0;
4601       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4602       break;
4603     case VariantTwoKings:
4604       pieces = twoKingsArray;
4605       break;
4606     case VariantCapaRandom:
4607       shuffleOpenings = TRUE;
4608     case VariantCapablanca:
4609       pieces = CapablancaArray;
4610       gameInfo.boardWidth = 10;
4611       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4612       break;
4613     case VariantGothic:
4614       pieces = GothicArray;
4615       gameInfo.boardWidth = 10;
4616       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4617       break;
4618     case VariantJanus:
4619       pieces = JanusArray;
4620       gameInfo.boardWidth = 10;
4621       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4622       nrCastlingRights = 6;
4623         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4624         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4625         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4626         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4627         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4628         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4629       break;
4630     case VariantFalcon:
4631       pieces = FalconArray;
4632       gameInfo.boardWidth = 10;
4633       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4634       break;
4635     case VariantXiangqi:
4636       pieces = XiangqiArray;
4637       gameInfo.boardWidth  = 9;
4638       gameInfo.boardHeight = 10;
4639       nrCastlingRights = 0;
4640       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4641       break;
4642     case VariantShogi:
4643       pieces = ShogiArray;
4644       gameInfo.boardWidth  = 9;
4645       gameInfo.boardHeight = 9;
4646       gameInfo.holdingsSize = 7;
4647       nrCastlingRights = 0;
4648       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4649       break;
4650     case VariantCourier:
4651       pieces = CourierArray;
4652       gameInfo.boardWidth  = 12;
4653       nrCastlingRights = 0;
4654       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4655       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4656       break;
4657     case VariantKnightmate:
4658       pieces = KnightmateArray;
4659       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4660       break;
4661     case VariantFairy:
4662       pieces = fairyArray;
4663       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4664       break;
4665     case VariantGreat:
4666       pieces = GreatArray;
4667       gameInfo.boardWidth = 10;
4668       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4669       gameInfo.holdingsSize = 8;
4670       break;
4671     case VariantSuper:
4672       pieces = FIDEArray;
4673       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4674       gameInfo.holdingsSize = 8;
4675       startedFromSetupPosition = TRUE;
4676       break;
4677     case VariantCrazyhouse:
4678     case VariantBughouse:
4679       pieces = FIDEArray;
4680       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4681       gameInfo.holdingsSize = 5;
4682       break;
4683     case VariantWildCastle:
4684       pieces = FIDEArray;
4685       /* !!?shuffle with kings guaranteed to be on d or e file */
4686       shuffleOpenings = 1;
4687       break;
4688     case VariantNoCastle:
4689       pieces = FIDEArray;
4690       nrCastlingRights = 0;
4691       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4692       /* !!?unconstrained back-rank shuffle */
4693       shuffleOpenings = 1;
4694       break;
4695     }
4696
4697     overrule = 0;
4698     if(appData.NrFiles >= 0) {
4699         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4700         gameInfo.boardWidth = appData.NrFiles;
4701     }
4702     if(appData.NrRanks >= 0) {
4703         gameInfo.boardHeight = appData.NrRanks;
4704     }
4705     if(appData.holdingsSize >= 0) {
4706         i = appData.holdingsSize;
4707         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4708         gameInfo.holdingsSize = i;
4709     }
4710     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4711     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4712         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4713
4714     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4715     if(pawnRow < 1) pawnRow = 1;
4716
4717     /* User pieceToChar list overrules defaults */
4718     if(appData.pieceToCharTable != NULL)
4719         SetCharTable(pieceToChar, appData.pieceToCharTable);
4720
4721     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4722
4723         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4724             s = (ChessSquare) 0; /* account holding counts in guard band */
4725         for( i=0; i<BOARD_HEIGHT; i++ )
4726             initialPosition[i][j] = s;
4727
4728         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4729         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4730         initialPosition[pawnRow][j] = WhitePawn;
4731         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4732         if(gameInfo.variant == VariantXiangqi) {
4733             if(j&1) {
4734                 initialPosition[pawnRow][j] = 
4735                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4736                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4737                    initialPosition[2][j] = WhiteCannon;
4738                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4739                 }
4740             }
4741         }
4742         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4743     }
4744     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4745
4746             j=BOARD_LEFT+1;
4747             initialPosition[1][j] = WhiteBishop;
4748             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4749             j=BOARD_RGHT-2;
4750             initialPosition[1][j] = WhiteRook;
4751             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4752     }
4753
4754     if( nrCastlingRights == -1) {
4755         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4756         /*       This sets default castling rights from none to normal corners   */
4757         /* Variants with other castling rights must set them themselves above    */
4758         nrCastlingRights = 6;
4759        
4760         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4761         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4762         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4763         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4764         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4765         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4766      }
4767
4768      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4769      if(gameInfo.variant == VariantGreat) { // promotion commoners
4770         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4771         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4772         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4773         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4774      }
4775 #if 0
4776     if(gameInfo.variant == VariantFischeRandom) {
4777       if( appData.defaultFrcPosition < 0 ) {
4778         ShuffleFRC( initialPosition );
4779       }
4780       else {
4781         SetupFRC( initialPosition, appData.defaultFrcPosition );
4782       }
4783       startedFromSetupPosition = TRUE;
4784     } else 
4785 #else
4786   if (appData.debugMode) {
4787     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4788   }
4789     if(shuffleOpenings) {
4790         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4791         startedFromSetupPosition = TRUE;
4792     }
4793 #endif
4794     if(startedFromPositionFile) {
4795       /* [HGM] loadPos: use PositionFile for every new game */
4796       CopyBoard(initialPosition, filePosition);
4797       for(i=0; i<nrCastlingRights; i++)
4798           castlingRights[0][i] = initialRights[i] = fileRights[i];
4799       startedFromSetupPosition = TRUE;
4800     }
4801
4802     CopyBoard(boards[0], initialPosition);
4803
4804     if(oldx != gameInfo.boardWidth ||
4805        oldy != gameInfo.boardHeight ||
4806        oldh != gameInfo.holdingsWidth
4807 #ifdef GOTHIC
4808        || oldv == VariantGothic ||        // For licensing popups
4809        gameInfo.variant == VariantGothic
4810 #endif
4811 #ifdef FALCON
4812        || oldv == VariantFalcon ||
4813        gameInfo.variant == VariantFalcon
4814 #endif
4815                                          )
4816             InitDrawingSizes(-2 ,0);
4817
4818     if (redraw)
4819       DrawPosition(TRUE, boards[currentMove]);
4820 }
4821
4822 void
4823 SendBoard(cps, moveNum)
4824      ChessProgramState *cps;
4825      int moveNum;
4826 {
4827     char message[MSG_SIZ];
4828     
4829     if (cps->useSetboard) {
4830       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4831       sprintf(message, "setboard %s\n", fen);
4832       SendToProgram(message, cps);
4833       free(fen);
4834
4835     } else {
4836       ChessSquare *bp;
4837       int i, j;
4838       /* Kludge to set black to move, avoiding the troublesome and now
4839        * deprecated "black" command.
4840        */
4841       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4842
4843       SendToProgram("edit\n", cps);
4844       SendToProgram("#\n", cps);
4845       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4846         bp = &boards[moveNum][i][BOARD_LEFT];
4847         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4848           if ((int) *bp < (int) BlackPawn) {
4849             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4850                     AAA + j, ONE + i);
4851             if(message[0] == '+' || message[0] == '~') {
4852                 sprintf(message, "%c%c%c+\n",
4853                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4854                         AAA + j, ONE + i);
4855             }
4856             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4857                 message[1] = BOARD_RGHT   - 1 - j + '1';
4858                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4859             }
4860             SendToProgram(message, cps);
4861           }
4862         }
4863       }
4864     
4865       SendToProgram("c\n", cps);
4866       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4867         bp = &boards[moveNum][i][BOARD_LEFT];
4868         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4869           if (((int) *bp != (int) EmptySquare)
4870               && ((int) *bp >= (int) BlackPawn)) {
4871             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4872                     AAA + j, ONE + i);
4873             if(message[0] == '+' || message[0] == '~') {
4874                 sprintf(message, "%c%c%c+\n",
4875                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4876                         AAA + j, ONE + i);
4877             }
4878             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4879                 message[1] = BOARD_RGHT   - 1 - j + '1';
4880                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4881             }
4882             SendToProgram(message, cps);
4883           }
4884         }
4885       }
4886     
4887       SendToProgram(".\n", cps);
4888     }
4889     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4890 }
4891
4892 int
4893 IsPromotion(fromX, fromY, toX, toY)
4894      int fromX, fromY, toX, toY;
4895 {
4896     /* [HGM] add Shogi promotions */
4897     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4898     ChessSquare piece;
4899
4900     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4901       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4902    /* [HGM] Note to self: line above also weeds out drops */
4903     piece = boards[currentMove][fromY][fromX];
4904     if(gameInfo.variant == VariantShogi) {
4905         promotionZoneSize = 3;
4906         highestPromotingPiece = (int)WhiteKing;
4907         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4908            and if in normal chess we then allow promotion to King, why not
4909            allow promotion of other piece in Shogi?                         */
4910     }
4911     if((int)piece >= BlackPawn) {
4912         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4913              return FALSE;
4914         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4915     } else {
4916         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4917            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4918     }
4919     return ( (int)piece <= highestPromotingPiece );
4920 }
4921
4922 int
4923 InPalace(row, column)
4924      int row, column;
4925 {   /* [HGM] for Xiangqi */
4926     if( (row < 3 || row > BOARD_HEIGHT-4) &&
4927          column < (BOARD_WIDTH + 4)/2 &&
4928          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4929     return FALSE;
4930 }
4931
4932 int
4933 PieceForSquare (x, y)
4934      int x;
4935      int y;
4936 {
4937   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4938      return -1;
4939   else
4940      return boards[currentMove][y][x];
4941 }
4942
4943 int
4944 OKToStartUserMove(x, y)
4945      int x, y;
4946 {
4947     ChessSquare from_piece;
4948     int white_piece;
4949
4950     if (matchMode) return FALSE;
4951     if (gameMode == EditPosition) return TRUE;
4952
4953     if (x >= 0 && y >= 0)
4954       from_piece = boards[currentMove][y][x];
4955     else
4956       from_piece = EmptySquare;
4957
4958     if (from_piece == EmptySquare) return FALSE;
4959
4960     white_piece = (int)from_piece >= (int)WhitePawn &&
4961       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4962
4963     switch (gameMode) {
4964       case PlayFromGameFile:
4965       case AnalyzeFile:
4966       case TwoMachinesPlay:
4967       case EndOfGame:
4968         return FALSE;
4969
4970       case IcsObserving:
4971       case IcsIdle:
4972         return FALSE;
4973
4974       case MachinePlaysWhite:
4975       case IcsPlayingBlack:
4976         if (appData.zippyPlay) return FALSE;
4977         if (white_piece) {
4978             DisplayMoveError(_("You are playing Black"));
4979             return FALSE;
4980         }
4981         break;
4982
4983       case MachinePlaysBlack:
4984       case IcsPlayingWhite:
4985         if (appData.zippyPlay) return FALSE;
4986         if (!white_piece) {
4987             DisplayMoveError(_("You are playing White"));
4988             return FALSE;
4989         }
4990         break;
4991
4992       case EditGame:
4993         if (!white_piece && WhiteOnMove(currentMove)) {
4994             DisplayMoveError(_("It is White's turn"));
4995             return FALSE;
4996         }           
4997         if (white_piece && !WhiteOnMove(currentMove)) {
4998             DisplayMoveError(_("It is Black's turn"));
4999             return FALSE;
5000         }           
5001         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5002             /* Editing correspondence game history */
5003             /* Could disallow this or prompt for confirmation */
5004             cmailOldMove = -1;
5005         }
5006         if (currentMove < forwardMostMove) {
5007             /* Discarding moves */
5008             /* Could prompt for confirmation here,
5009                but I don't think that's such a good idea */
5010             forwardMostMove = currentMove;
5011         }
5012         break;
5013
5014       case BeginningOfGame:
5015         if (appData.icsActive) return FALSE;
5016         if (!appData.noChessProgram) {
5017             if (!white_piece) {
5018                 DisplayMoveError(_("You are playing White"));
5019                 return FALSE;
5020             }
5021         }
5022         break;
5023         
5024       case Training:
5025         if (!white_piece && WhiteOnMove(currentMove)) {
5026             DisplayMoveError(_("It is White's turn"));
5027             return FALSE;
5028         }           
5029         if (white_piece && !WhiteOnMove(currentMove)) {
5030             DisplayMoveError(_("It is Black's turn"));
5031             return FALSE;
5032         }           
5033         break;
5034
5035       default:
5036       case IcsExamining:
5037         break;
5038     }
5039     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5040         && gameMode != AnalyzeFile && gameMode != Training) {
5041         DisplayMoveError(_("Displayed position is not current"));
5042         return FALSE;
5043     }
5044     return TRUE;
5045 }
5046
5047 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5048 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5049 int lastLoadGameUseList = FALSE;
5050 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5051 ChessMove lastLoadGameStart = (ChessMove) 0;
5052
5053
5054 ChessMove
5055 UserMoveTest(fromX, fromY, toX, toY, promoChar)
5056      int fromX, fromY, toX, toY;
5057      int promoChar;
5058 {
5059     ChessMove moveType;
5060     ChessSquare pdown, pup;
5061
5062     if (fromX < 0 || fromY < 0) return ImpossibleMove;
5063     if ((fromX == toX) && (fromY == toY)) {
5064         return ImpossibleMove;
5065     }
5066
5067     /* [HGM] suppress all moves into holdings area and guard band */
5068     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5069             return ImpossibleMove;
5070
5071     /* [HGM] <sameColor> moved to here from winboard.c */
5072     /* note: this code seems to exist for filtering out some obviously illegal premoves */
5073     pdown = boards[currentMove][fromY][fromX];
5074     pup = boards[currentMove][toY][toX];
5075     if (    gameMode != EditPosition &&
5076             (WhitePawn <= pdown && pdown < BlackPawn &&
5077              WhitePawn <= pup && pup < BlackPawn  ||
5078              BlackPawn <= pdown && pdown < EmptySquare &&
5079              BlackPawn <= pup && pup < EmptySquare 
5080             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5081                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5082                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) 
5083         )           )
5084          return ImpossibleMove;
5085
5086     /* Check if the user is playing in turn.  This is complicated because we
5087        let the user "pick up" a piece before it is his turn.  So the piece he
5088        tried to pick up may have been captured by the time he puts it down!
5089        Therefore we use the color the user is supposed to be playing in this
5090        test, not the color of the piece that is currently on the starting
5091        square---except in EditGame mode, where the user is playing both
5092        sides; fortunately there the capture race can't happen.  (It can
5093        now happen in IcsExamining mode, but that's just too bad.  The user
5094        will get a somewhat confusing message in that case.)
5095        */
5096
5097     switch (gameMode) {
5098       case PlayFromGameFile:
5099       case AnalyzeFile:
5100       case TwoMachinesPlay:
5101       case EndOfGame:
5102       case IcsObserving:
5103       case IcsIdle:
5104         /* We switched into a game mode where moves are not accepted,
5105            perhaps while the mouse button was down. */
5106         return ImpossibleMove;
5107
5108       case MachinePlaysWhite:
5109         /* User is moving for Black */
5110         if (WhiteOnMove(currentMove)) {
5111             DisplayMoveError(_("It is White's turn"));
5112             return ImpossibleMove;
5113         }
5114         break;
5115
5116       case MachinePlaysBlack:
5117         /* User is moving for White */
5118         if (!WhiteOnMove(currentMove)) {
5119             DisplayMoveError(_("It is Black's turn"));
5120             return ImpossibleMove;
5121         }
5122         break;
5123
5124       case EditGame:
5125       case IcsExamining:
5126       case BeginningOfGame:
5127       case AnalyzeMode:
5128       case Training:
5129         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5130             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5131             /* User is moving for Black */
5132             if (WhiteOnMove(currentMove)) {
5133                 DisplayMoveError(_("It is White's turn"));
5134                 return ImpossibleMove;
5135             }
5136         } else {
5137             /* User is moving for White */
5138             if (!WhiteOnMove(currentMove)) {
5139                 DisplayMoveError(_("It is Black's turn"));
5140                 return ImpossibleMove;
5141             }
5142         }
5143         break;
5144
5145       case IcsPlayingBlack:
5146         /* User is moving for Black */
5147         if (WhiteOnMove(currentMove)) {
5148             if (!appData.premove) {
5149                 DisplayMoveError(_("It is White's turn"));
5150             } else if (toX >= 0 && toY >= 0) {
5151                 premoveToX = toX;
5152                 premoveToY = toY;
5153                 premoveFromX = fromX;
5154                 premoveFromY = fromY;
5155                 premovePromoChar = promoChar;
5156                 gotPremove = 1;
5157                 if (appData.debugMode) 
5158                     fprintf(debugFP, "Got premove: fromX %d,"
5159                             "fromY %d, toX %d, toY %d\n",
5160                             fromX, fromY, toX, toY);
5161             }
5162             return ImpossibleMove;
5163         }
5164         break;
5165
5166       case IcsPlayingWhite:
5167         /* User is moving for White */
5168         if (!WhiteOnMove(currentMove)) {
5169             if (!appData.premove) {
5170                 DisplayMoveError(_("It is Black's turn"));
5171             } else if (toX >= 0 && toY >= 0) {
5172                 premoveToX = toX;
5173                 premoveToY = toY;
5174                 premoveFromX = fromX;
5175                 premoveFromY = fromY;
5176                 premovePromoChar = promoChar;
5177                 gotPremove = 1;
5178                 if (appData.debugMode) 
5179                     fprintf(debugFP, "Got premove: fromX %d,"
5180                             "fromY %d, toX %d, toY %d\n",
5181                             fromX, fromY, toX, toY);
5182             }
5183             return ImpossibleMove;
5184         }
5185         break;
5186
5187       default:
5188         break;
5189
5190       case EditPosition:
5191         /* EditPosition, empty square, or different color piece;
5192            click-click move is possible */
5193         if (toX == -2 || toY == -2) {
5194             boards[0][fromY][fromX] = EmptySquare;
5195             return AmbiguousMove;
5196         } else if (toX >= 0 && toY >= 0) {
5197             boards[0][toY][toX] = boards[0][fromY][fromX];
5198             boards[0][fromY][fromX] = EmptySquare;
5199             return AmbiguousMove;
5200         }
5201         return ImpossibleMove;
5202     }
5203
5204     /* [HGM] If move started in holdings, it means a drop */
5205     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5206          if( pup != EmptySquare ) return ImpossibleMove;
5207          if(appData.testLegality) {
5208              /* it would be more logical if LegalityTest() also figured out
5209               * which drops are legal. For now we forbid pawns on back rank.
5210               * Shogi is on its own here...
5211               */
5212              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5213                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5214                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5215          }
5216          return WhiteDrop; /* Not needed to specify white or black yet */
5217     }
5218
5219     userOfferedDraw = FALSE;
5220         
5221     /* [HGM] always test for legality, to get promotion info */
5222     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5223                           epStatus[currentMove], castlingRights[currentMove],
5224                                          fromY, fromX, toY, toX, promoChar);
5225
5226     /* [HGM] but possibly ignore an IllegalMove result */
5227     if (appData.testLegality) {
5228         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5229             DisplayMoveError(_("Illegal move"));
5230             return ImpossibleMove;
5231         }
5232     }
5233 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5234     return moveType;
5235     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5236        function is made into one that returns an OK move type if FinishMove
5237        should be called. This to give the calling driver routine the
5238        opportunity to finish the userMove input with a promotion popup,
5239        without bothering the user with this for invalid or illegal moves */
5240
5241 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5242 }
5243
5244 /* Common tail of UserMoveEvent and DropMenuEvent */
5245 int
5246 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5247      ChessMove moveType;
5248      int fromX, fromY, toX, toY;
5249      /*char*/int promoChar;
5250 {
5251     char *bookHit = 0;
5252 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5253     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5254         // [HGM] superchess: suppress promotions to non-available piece
5255         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5256         if(WhiteOnMove(currentMove)) {
5257             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5258         } else {
5259             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5260         }
5261     }
5262
5263     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5264        move type in caller when we know the move is a legal promotion */
5265     if(moveType == NormalMove && promoChar)
5266         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5267 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5268     /* [HGM] convert drag-and-drop piece drops to standard form */
5269     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5270          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5271            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5272                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5273 //         fromX = boards[currentMove][fromY][fromX];
5274            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5275            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5276            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5277            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5278          fromY = DROP_RANK;
5279     }
5280
5281     /* [HGM] <popupFix> The following if has been moved here from
5282        UserMoveEvent(). Because it seemed to belon here (why not allow
5283        piece drops in training games?), and because it can only be
5284        performed after it is known to what we promote. */
5285     if (gameMode == Training) {
5286       /* compare the move played on the board to the next move in the
5287        * game. If they match, display the move and the opponent's response. 
5288        * If they don't match, display an error message.
5289        */
5290       int saveAnimate;
5291       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5292       CopyBoard(testBoard, boards[currentMove]);
5293       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5294
5295       if (CompareBoards(testBoard, boards[currentMove+1])) {
5296         ForwardInner(currentMove+1);
5297
5298         /* Autoplay the opponent's response.
5299          * if appData.animate was TRUE when Training mode was entered,
5300          * the response will be animated.
5301          */
5302         saveAnimate = appData.animate;
5303         appData.animate = animateTraining;
5304         ForwardInner(currentMove+1);
5305         appData.animate = saveAnimate;
5306
5307         /* check for the end of the game */
5308         if (currentMove >= forwardMostMove) {
5309           gameMode = PlayFromGameFile;
5310           ModeHighlight();
5311           SetTrainingModeOff();
5312           DisplayInformation(_("End of game"));
5313         }
5314       } else {
5315         DisplayError(_("Incorrect move"), 0);
5316       }
5317       return 1;
5318     }
5319
5320   /* Ok, now we know that the move is good, so we can kill
5321      the previous line in Analysis Mode */
5322   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5323     forwardMostMove = currentMove;
5324   }
5325
5326   /* If we need the chess program but it's dead, restart it */
5327   ResurrectChessProgram();
5328
5329   /* A user move restarts a paused game*/
5330   if (pausing)
5331     PauseEvent();
5332
5333   thinkOutput[0] = NULLCHAR;
5334
5335   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5336
5337   if (gameMode == BeginningOfGame) {
5338     if (appData.noChessProgram) {
5339       gameMode = EditGame;
5340       SetGameInfo();
5341     } else {
5342       char buf[MSG_SIZ];
5343       gameMode = MachinePlaysBlack;
5344       StartClocks();
5345       SetGameInfo();
5346       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5347       DisplayTitle(buf);
5348       if (first.sendName) {
5349         sprintf(buf, "name %s\n", gameInfo.white);
5350         SendToProgram(buf, &first);
5351       }
5352       StartClocks();
5353     }
5354     ModeHighlight();
5355   }
5356 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5357   /* Relay move to ICS or chess engine */
5358   if (appData.icsActive) {
5359     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5360         gameMode == IcsExamining) {
5361       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5362       ics_user_moved = 1;
5363     }
5364   } else {
5365     if (first.sendTime && (gameMode == BeginningOfGame ||
5366                            gameMode == MachinePlaysWhite ||
5367                            gameMode == MachinePlaysBlack)) {
5368       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5369     }
5370     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5371          // [HGM] book: if program might be playing, let it use book
5372         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5373         first.maybeThinking = TRUE;
5374     } else SendMoveToProgram(forwardMostMove-1, &first);
5375     if (currentMove == cmailOldMove + 1) {
5376       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5377     }
5378   }
5379
5380   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5381
5382   switch (gameMode) {
5383   case EditGame:
5384     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5385                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5386     case MT_NONE:
5387     case MT_CHECK:
5388       break;
5389     case MT_CHECKMATE:
5390     case MT_STAINMATE:
5391       if (WhiteOnMove(currentMove)) {
5392         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5393       } else {
5394         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5395       }
5396       break;
5397     case MT_STALEMATE:
5398       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5399       break;
5400     }
5401     break;
5402     
5403   case MachinePlaysBlack:
5404   case MachinePlaysWhite:
5405     /* disable certain menu options while machine is thinking */
5406     SetMachineThinkingEnables();
5407     break;
5408
5409   default:
5410     break;
5411   }
5412
5413   if(bookHit) { // [HGM] book: simulate book reply
5414         static char bookMove[MSG_SIZ]; // a bit generous?
5415
5416         programStats.nodes = programStats.depth = programStats.time = 
5417         programStats.score = programStats.got_only_move = 0;
5418         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5419
5420         strcpy(bookMove, "move ");
5421         strcat(bookMove, bookHit);
5422         HandleMachineMove(bookMove, &first);
5423   }
5424   return 1;
5425 }
5426
5427 void
5428 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5429      int fromX, fromY, toX, toY;
5430      int promoChar;
5431 {
5432     /* [HGM] This routine was added to allow calling of its two logical
5433        parts from other modules in the old way. Before, UserMoveEvent()
5434        automatically called FinishMove() if the move was OK, and returned
5435        otherwise. I separated the two, in order to make it possible to
5436        slip a promotion popup in between. But that it always needs two
5437        calls, to the first part, (now called UserMoveTest() ), and to
5438        FinishMove if the first part succeeded. Calls that do not need
5439        to do anything in between, can call this routine the old way. 
5440     */
5441     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
5442 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5443     if(moveType != ImpossibleMove)
5444         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5445 }
5446
5447 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5448 {
5449 //    char * hint = lastHint;
5450     FrontEndProgramStats stats;
5451
5452     stats.which = cps == &first ? 0 : 1;
5453     stats.depth = cpstats->depth;
5454     stats.nodes = cpstats->nodes;
5455     stats.score = cpstats->score;
5456     stats.time = cpstats->time;
5457     stats.pv = cpstats->movelist;
5458     stats.hint = lastHint;
5459     stats.an_move_index = 0;
5460     stats.an_move_count = 0;
5461
5462     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5463         stats.hint = cpstats->move_name;
5464         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5465         stats.an_move_count = cpstats->nr_moves;
5466     }
5467
5468     SetProgramStats( &stats );
5469 }
5470
5471 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5472 {   // [HGM] book: this routine intercepts moves to simulate book replies
5473     char *bookHit = NULL;
5474
5475     //first determine if the incoming move brings opponent into his book
5476     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5477         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5478     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5479     if(bookHit != NULL && !cps->bookSuspend) {
5480         // make sure opponent is not going to reply after receiving move to book position
5481         SendToProgram("force\n", cps);
5482         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5483     }
5484     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5485     // now arrange restart after book miss
5486     if(bookHit) {
5487         // after a book hit we never send 'go', and the code after the call to this routine
5488         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5489         char buf[MSG_SIZ];
5490         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5491         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5492         SendToProgram(buf, cps);
5493         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5494     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5495         SendToProgram("go\n", cps);
5496         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5497     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5498         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5499             SendToProgram("go\n", cps); 
5500         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5501     }
5502     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5503 }
5504
5505 char *savedMessage;
5506 ChessProgramState *savedState;
5507 void DeferredBookMove(void)
5508 {
5509         if(savedState->lastPing != savedState->lastPong)
5510                     ScheduleDelayedEvent(DeferredBookMove, 10);
5511         else
5512         HandleMachineMove(savedMessage, savedState);
5513 }
5514
5515 void
5516 HandleMachineMove(message, cps)
5517      char *message;
5518      ChessProgramState *cps;
5519 {
5520     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5521     char realname[MSG_SIZ];
5522     int fromX, fromY, toX, toY;
5523     ChessMove moveType;
5524     char promoChar;
5525     char *p;
5526     int machineWhite;
5527     char *bookHit;
5528
5529 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5530     /*
5531      * Kludge to ignore BEL characters
5532      */
5533     while (*message == '\007') message++;
5534
5535     /*
5536      * [HGM] engine debug message: ignore lines starting with '#' character
5537      */
5538     if(cps->debug && *message == '#') return;
5539
5540     /*
5541      * Look for book output
5542      */
5543     if (cps == &first && bookRequested) {
5544         if (message[0] == '\t' || message[0] == ' ') {
5545             /* Part of the book output is here; append it */
5546             strcat(bookOutput, message);
5547             strcat(bookOutput, "  \n");
5548             return;
5549         } else if (bookOutput[0] != NULLCHAR) {
5550             /* All of book output has arrived; display it */
5551             char *p = bookOutput;
5552             while (*p != NULLCHAR) {
5553                 if (*p == '\t') *p = ' ';
5554                 p++;
5555             }
5556             DisplayInformation(bookOutput);
5557             bookRequested = FALSE;
5558             /* Fall through to parse the current output */
5559         }
5560     }
5561
5562     /*
5563      * Look for machine move.
5564      */
5565     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5566         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5567     {
5568         /* This method is only useful on engines that support ping */
5569         if (cps->lastPing != cps->lastPong) {
5570           if (gameMode == BeginningOfGame) {
5571             /* Extra move from before last new; ignore */
5572             if (appData.debugMode) {
5573                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5574             }
5575           } else {
5576             if (appData.debugMode) {
5577                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5578                         cps->which, gameMode);
5579             }
5580
5581             SendToProgram("undo\n", cps);
5582           }
5583           return;
5584         }
5585
5586         switch (gameMode) {
5587           case BeginningOfGame:
5588             /* Extra move from before last reset; ignore */
5589             if (appData.debugMode) {
5590                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5591             }
5592             return;
5593
5594           case EndOfGame:
5595           case IcsIdle:
5596           default:
5597             /* Extra move after we tried to stop.  The mode test is
5598                not a reliable way of detecting this problem, but it's
5599                the best we can do on engines that don't support ping.
5600             */
5601             if (appData.debugMode) {
5602                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5603                         cps->which, gameMode);
5604             }
5605             SendToProgram("undo\n", cps);
5606             return;
5607
5608           case MachinePlaysWhite:
5609           case IcsPlayingWhite:
5610             machineWhite = TRUE;
5611             break;
5612
5613           case MachinePlaysBlack:
5614           case IcsPlayingBlack:
5615             machineWhite = FALSE;
5616             break;
5617
5618           case TwoMachinesPlay:
5619             machineWhite = (cps->twoMachinesColor[0] == 'w');
5620             break;
5621         }
5622         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5623             if (appData.debugMode) {
5624                 fprintf(debugFP,
5625                         "Ignoring move out of turn by %s, gameMode %d"
5626                         ", forwardMost %d\n",
5627                         cps->which, gameMode, forwardMostMove);
5628             }
5629             return;
5630         }
5631
5632     if (appData.debugMode) { int f = forwardMostMove;
5633         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5634                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5635     }
5636         if(cps->alphaRank) AlphaRank(machineMove, 4);
5637         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5638                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5639             /* Machine move could not be parsed; ignore it. */
5640             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5641                     machineMove, cps->which);
5642             DisplayError(buf1, 0);
5643             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5644                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5645             if (gameMode == TwoMachinesPlay) {
5646               GameEnds(machineWhite ? BlackWins : WhiteWins,
5647                        buf1, GE_XBOARD);
5648             }
5649             return;
5650         }
5651
5652         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5653         /* So we have to redo legality test with true e.p. status here,  */
5654         /* to make sure an illegal e.p. capture does not slip through,   */
5655         /* to cause a forfeit on a justified illegal-move complaint      */
5656         /* of the opponent.                                              */
5657         if( gameMode==TwoMachinesPlay && appData.testLegality
5658             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5659                                                               ) {
5660            ChessMove moveType;
5661            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5662                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5663                              fromY, fromX, toY, toX, promoChar);
5664             if (appData.debugMode) {
5665                 int i;
5666                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5667                     castlingRights[forwardMostMove][i], castlingRank[i]);
5668                 fprintf(debugFP, "castling rights\n");
5669             }
5670             if(moveType == IllegalMove) {
5671                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5672                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5673                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5674                            buf1, GE_XBOARD);
5675                 return;
5676            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5677            /* [HGM] Kludge to handle engines that send FRC-style castling
5678               when they shouldn't (like TSCP-Gothic) */
5679            switch(moveType) {
5680              case WhiteASideCastleFR:
5681              case BlackASideCastleFR:
5682                toX+=2;
5683                currentMoveString[2]++;
5684                break;
5685              case WhiteHSideCastleFR:
5686              case BlackHSideCastleFR:
5687                toX--;
5688                currentMoveString[2]--;
5689                break;
5690              default: ; // nothing to do, but suppresses warning of pedantic compilers
5691            }
5692         }
5693         hintRequested = FALSE;
5694         lastHint[0] = NULLCHAR;
5695         bookRequested = FALSE;
5696         /* Program may be pondering now */
5697         cps->maybeThinking = TRUE;
5698         if (cps->sendTime == 2) cps->sendTime = 1;
5699         if (cps->offeredDraw) cps->offeredDraw--;
5700
5701 #if ZIPPY
5702         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5703             first.initDone) {
5704           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5705           ics_user_moved = 1;
5706           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5707                 char buf[3*MSG_SIZ];
5708
5709                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5710                         programStats.score / 100.,
5711                         programStats.depth,
5712                         programStats.time / 100.,
5713                         (unsigned int)programStats.nodes,
5714                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5715                         programStats.movelist);
5716                 SendToICS(buf);
5717 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5718           }
5719         }
5720 #endif
5721         /* currentMoveString is set as a side-effect of ParseOneMove */
5722         strcpy(machineMove, currentMoveString);
5723         strcat(machineMove, "\n");
5724         strcpy(moveList[forwardMostMove], machineMove);
5725
5726         /* [AS] Save move info and clear stats for next move */
5727         pvInfoList[ forwardMostMove ].score = programStats.score;
5728         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5729         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5730         ClearProgramStats();
5731         thinkOutput[0] = NULLCHAR;
5732         hiddenThinkOutputState = 0;
5733
5734         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5735
5736         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5737         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5738             int count = 0;
5739
5740             while( count < adjudicateLossPlies ) {
5741                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5742
5743                 if( count & 1 ) {
5744                     score = -score; /* Flip score for winning side */
5745                 }
5746
5747                 if( score > adjudicateLossThreshold ) {
5748                     break;
5749                 }
5750
5751                 count++;
5752             }
5753
5754             if( count >= adjudicateLossPlies ) {
5755                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5756
5757                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5758                     "Xboard adjudication", 
5759                     GE_XBOARD );
5760
5761                 return;
5762             }
5763         }
5764
5765         if( gameMode == TwoMachinesPlay ) {
5766           // [HGM] some adjudications useful with buggy engines
5767             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5768           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5769
5770
5771             if( appData.testLegality )
5772             {   /* [HGM] Some more adjudications for obstinate engines */
5773                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5774                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5775                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5776                 static int moveCount = 6;
5777                 ChessMove result;
5778                 char *reason = NULL;
5779
5780                 /* Count what is on board. */
5781                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5782                 {   ChessSquare p = boards[forwardMostMove][i][j];
5783                     int m=i;
5784
5785                     switch((int) p)
5786                     {   /* count B,N,R and other of each side */
5787                         case WhiteKing:
5788                         case BlackKing:
5789                              NrK++; break; // [HGM] atomic: count Kings
5790                         case WhiteKnight:
5791                              NrWN++; break;
5792                         case WhiteBishop:
5793                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5794                              bishopsColor |= 1 << ((i^j)&1);
5795                              NrWB++; break;
5796                         case BlackKnight:
5797                              NrBN++; break;
5798                         case BlackBishop:
5799                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5800                              bishopsColor |= 1 << ((i^j)&1);
5801                              NrBB++; break;
5802                         case WhiteRook:
5803                              NrWR++; break;
5804                         case BlackRook:
5805                              NrBR++; break;
5806                         case WhiteQueen:
5807                              NrWQ++; break;
5808                         case BlackQueen:
5809                              NrBQ++; break;
5810                         case EmptySquare: 
5811                              break;
5812                         case BlackPawn:
5813                              m = 7-i;
5814                         case WhitePawn:
5815                              PawnAdvance += m; NrPawns++;
5816                     }
5817                     NrPieces += (p != EmptySquare);
5818                     NrW += ((int)p < (int)BlackPawn);
5819                     if(gameInfo.variant == VariantXiangqi && 
5820                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5821                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5822                         NrW -= ((int)p < (int)BlackPawn);
5823                     }
5824                 }
5825
5826                 /* Some material-based adjudications that have to be made before stalemate test */
5827                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5828                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5829                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5830                      if(appData.checkMates) {
5831                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5832                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5833                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
5834                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
5835                          return;
5836                      }
5837                 }
5838
5839                 /* Bare King in Shatranj (loses) or Losers (wins) */
5840                 if( NrW == 1 || NrPieces - NrW == 1) {
5841                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5842                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
5843                      if(appData.checkMates) {
5844                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5845                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5846                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5847                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5848                          return;
5849                      }
5850                   } else
5851                   if( gameInfo.variant == VariantShatranj && --bare < 0)
5852                   {    /* bare King */
5853                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5854                         if(appData.checkMates) {
5855                             /* but only adjudicate if adjudication enabled */
5856                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5857                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5858                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
5859                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5860                             return;
5861                         }
5862                   }
5863                 } else bare = 1;
5864
5865
5866             // don't wait for engine to announce game end if we can judge ourselves
5867             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5868                                        castlingRights[forwardMostMove]) ) {
5869               case MT_CHECK:
5870                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5871                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
5872                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5873                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5874                             checkCnt++;
5875                         if(checkCnt >= 2) {
5876                             reason = "Xboard adjudication: 3rd check";
5877                             epStatus[forwardMostMove] = EP_CHECKMATE;
5878                             break;
5879                         }
5880                     }
5881                 }
5882               case MT_NONE:
5883               default:
5884                 break;
5885               case MT_STALEMATE:
5886               case MT_STAINMATE:
5887                 reason = "Xboard adjudication: Stalemate";
5888                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5889                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
5890                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5891                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
5892                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5893                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5894                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5895                                                                         EP_CHECKMATE : EP_WINS);
5896                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5897                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5898                 }
5899                 break;
5900               case MT_CHECKMATE:
5901                 reason = "Xboard adjudication: Checkmate";
5902                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5903                 break;
5904             }
5905
5906                 switch(i = epStatus[forwardMostMove]) {
5907                     case EP_STALEMATE:
5908                         result = GameIsDrawn; break;
5909                     case EP_CHECKMATE:
5910                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5911                     case EP_WINS:
5912                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5913                     default:
5914                         result = (ChessMove) 0;
5915                 }
5916                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5917                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5918                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5919                     GameEnds( result, reason, GE_XBOARD );
5920                     return;
5921                 }
5922
5923                 /* Next absolutely insufficient mating material. */
5924                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
5925                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5926                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5927                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5928                 {    /* KBK, KNK, KK of KBKB with like Bishops */
5929
5930                      /* always flag draws, for judging claims */
5931                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
5932
5933                      if(appData.materialDraws) {
5934                          /* but only adjudicate them if adjudication enabled */
5935                          SendToProgram("force\n", cps->other); // suppress reply
5936                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5937                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5938                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5939                          return;
5940                      }
5941                 }
5942
5943                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5944                 if(NrPieces == 4 && 
5945                    (   NrWR == 1 && NrBR == 1 /* KRKR */
5946                    || NrWQ==1 && NrBQ==1     /* KQKQ */
5947                    || NrWN==2 || NrBN==2     /* KNNK */
5948                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5949                   ) ) {
5950                      if(--moveCount < 0 && appData.trivialDraws)
5951                      {    /* if the first 3 moves do not show a tactical win, declare draw */
5952                           SendToProgram("force\n", cps->other); // suppress reply
5953                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5954                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5955                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5956                           return;
5957                      }
5958                 } else moveCount = 6;
5959             }
5960           }
5961 #if 1
5962     if (appData.debugMode) { int i;
5963       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5964               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5965               appData.drawRepeats);
5966       for( i=forwardMostMove; i>=backwardMostMove; i-- )
5967            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5968
5969     }
5970 #endif
5971                 /* Check for rep-draws */
5972                 count = 0;
5973                 for(k = forwardMostMove-2;
5974                     k>=backwardMostMove && k>=forwardMostMove-100 &&
5975                         epStatus[k] < EP_UNKNOWN &&
5976                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5977                     k-=2)
5978                 {   int rights=0;
5979 #if 0
5980     if (appData.debugMode) {
5981       fprintf(debugFP, " loop\n");
5982     }
5983 #endif
5984                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
5985 #if 0
5986     if (appData.debugMode) {
5987       fprintf(debugFP, "match\n");
5988     }
5989 #endif
5990                         /* compare castling rights */
5991                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5992                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5993                                 rights++; /* King lost rights, while rook still had them */
5994                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5995                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5996                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5997                                    rights++; /* but at least one rook lost them */
5998                         }
5999                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6000                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6001                                 rights++; 
6002                         if( castlingRights[forwardMostMove][5] >= 0 ) {
6003                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6004                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6005                                    rights++;
6006                         }
6007 #if 0
6008     if (appData.debugMode) {
6009       for(i=0; i<nrCastlingRights; i++)
6010       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
6011     }
6012
6013     if (appData.debugMode) {
6014       fprintf(debugFP, " %d %d\n", rights, k);
6015     }
6016 #endif
6017                         if( rights == 0 && ++count > appData.drawRepeats-2
6018                             && appData.drawRepeats > 1) {
6019                              /* adjudicate after user-specified nr of repeats */
6020                              SendToProgram("force\n", cps->other); // suppress reply
6021                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6022                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6023                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6024                                 // [HGM] xiangqi: check for forbidden perpetuals
6025                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6026                                 for(m=forwardMostMove; m>k; m-=2) {
6027                                     if(MateTest(boards[m], PosFlags(m), 
6028                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
6029                                         ourPerpetual = 0; // the current mover did not always check
6030                                     if(MateTest(boards[m-1], PosFlags(m-1), 
6031                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
6032                                         hisPerpetual = 0; // the opponent did not always check
6033                                 }
6034                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6035                                                                         ourPerpetual, hisPerpetual);
6036                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6037                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6038                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6039                                     return;
6040                                 }
6041                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6042                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6043                                 // Now check for perpetual chases
6044                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6045                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6046                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6047                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6048                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6049                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6050                                         return;
6051                                     }
6052                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6053                                         break; // Abort repetition-checking loop.
6054                                 }
6055                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6056                              }
6057                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6058                              return;
6059                         }
6060                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6061                              epStatus[forwardMostMove] = EP_REP_DRAW;
6062                     }
6063                 }
6064
6065                 /* Now we test for 50-move draws. Determine ply count */
6066                 count = forwardMostMove;
6067                 /* look for last irreversble move */
6068                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6069                     count--;
6070                 /* if we hit starting position, add initial plies */
6071                 if( count == backwardMostMove )
6072                     count -= initialRulePlies;
6073                 count = forwardMostMove - count; 
6074                 if( count >= 100)
6075                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6076                          /* this is used to judge if draw claims are legal */
6077                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6078                          SendToProgram("force\n", cps->other); // suppress reply
6079                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6080                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6081                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6082                          return;
6083                 }
6084
6085                 /* if draw offer is pending, treat it as a draw claim
6086                  * when draw condition present, to allow engines a way to
6087                  * claim draws before making their move to avoid a race
6088                  * condition occurring after their move
6089                  */
6090                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6091                          char *p = NULL;
6092                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6093                              p = "Draw claim: 50-move rule";
6094                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6095                              p = "Draw claim: 3-fold repetition";
6096                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6097                              p = "Draw claim: insufficient mating material";
6098                          if( p != NULL ) {
6099                              SendToProgram("force\n", cps->other); // suppress reply
6100                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6101                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6102                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6103                              return;
6104                          }
6105                 }
6106
6107
6108                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6109                     SendToProgram("force\n", cps->other); // suppress reply
6110                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6111                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6112
6113                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6114
6115                     return;
6116                 }
6117         }
6118
6119         bookHit = NULL;
6120         if (gameMode == TwoMachinesPlay) {
6121             /* [HGM] relaying draw offers moved to after reception of move */
6122             /* and interpreting offer as claim if it brings draw condition */
6123             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6124                 SendToProgram("draw\n", cps->other);
6125             }
6126             if (cps->other->sendTime) {
6127                 SendTimeRemaining(cps->other,
6128                                   cps->other->twoMachinesColor[0] == 'w');
6129             }
6130             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6131             if (firstMove && !bookHit) {
6132                 firstMove = FALSE;
6133                 if (cps->other->useColors) {
6134                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6135                 }
6136                 SendToProgram("go\n", cps->other);
6137             }
6138             cps->other->maybeThinking = TRUE;
6139         }
6140
6141         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6142         
6143         if (!pausing && appData.ringBellAfterMoves) {
6144             RingBell();
6145         }
6146
6147         /* 
6148          * Reenable menu items that were disabled while
6149          * machine was thinking
6150          */
6151         if (gameMode != TwoMachinesPlay)
6152             SetUserThinkingEnables();
6153
6154         // [HGM] book: after book hit opponent has received move and is now in force mode
6155         // force the book reply into it, and then fake that it outputted this move by jumping
6156         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6157         if(bookHit) {
6158                 static char bookMove[MSG_SIZ]; // a bit generous?
6159
6160                 strcpy(bookMove, "move ");
6161                 strcat(bookMove, bookHit);
6162                 message = bookMove;
6163                 cps = cps->other;
6164                 programStats.nodes = programStats.depth = programStats.time = 
6165                 programStats.score = programStats.got_only_move = 0;
6166                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6167
6168                 if(cps->lastPing != cps->lastPong) {
6169                     savedMessage = message; // args for deferred call
6170                     savedState = cps;
6171                     ScheduleDelayedEvent(DeferredBookMove, 10);
6172                     return;
6173                 }
6174                 goto FakeBookMove;
6175         }
6176
6177         return;
6178     }
6179
6180     /* Set special modes for chess engines.  Later something general
6181      *  could be added here; for now there is just one kludge feature,
6182      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6183      *  when "xboard" is given as an interactive command.
6184      */
6185     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6186         cps->useSigint = FALSE;
6187         cps->useSigterm = FALSE;
6188     }
6189     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6190       ParseFeatures(message+8, cps);
6191       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6192     }
6193
6194     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6195      * want this, I was asked to put it in, and obliged.
6196      */
6197     if (!strncmp(message, "setboard ", 9)) {
6198         Board initial_position; int i;
6199
6200         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6201
6202         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6203             DisplayError(_("Bad FEN received from engine"), 0);
6204             return ;
6205         } else {
6206            Reset(FALSE, FALSE);
6207            CopyBoard(boards[0], initial_position);
6208            initialRulePlies = FENrulePlies;
6209            epStatus[0] = FENepStatus;
6210            for( i=0; i<nrCastlingRights; i++ )
6211                 castlingRights[0][i] = FENcastlingRights[i];
6212            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6213            else gameMode = MachinePlaysBlack;                 
6214            DrawPosition(FALSE, boards[currentMove]);
6215         }
6216         return;
6217     }
6218
6219     /*
6220      * Look for communication commands
6221      */
6222     if (!strncmp(message, "telluser ", 9)) {
6223         DisplayNote(message + 9);
6224         return;
6225     }
6226     if (!strncmp(message, "tellusererror ", 14)) {
6227         DisplayError(message + 14, 0);
6228         return;
6229     }
6230     if (!strncmp(message, "tellopponent ", 13)) {
6231       if (appData.icsActive) {
6232         if (loggedOn) {
6233           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6234           SendToICS(buf1);
6235         }
6236       } else {
6237         DisplayNote(message + 13);
6238       }
6239       return;
6240     }
6241     if (!strncmp(message, "tellothers ", 11)) {
6242       if (appData.icsActive) {
6243         if (loggedOn) {
6244           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6245           SendToICS(buf1);
6246         }
6247       }
6248       return;
6249     }
6250     if (!strncmp(message, "tellall ", 8)) {
6251       if (appData.icsActive) {
6252         if (loggedOn) {
6253           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6254           SendToICS(buf1);
6255         }
6256       } else {
6257         DisplayNote(message + 8);
6258       }
6259       return;
6260     }
6261     if (strncmp(message, "warning", 7) == 0) {
6262         /* Undocumented feature, use tellusererror in new code */
6263         DisplayError(message, 0);
6264         return;
6265     }
6266     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6267         strcpy(realname, cps->tidy);
6268         strcat(realname, " query");
6269         AskQuestion(realname, buf2, buf1, cps->pr);
6270         return;
6271     }
6272     /* Commands from the engine directly to ICS.  We don't allow these to be 
6273      *  sent until we are logged on. Crafty kibitzes have been known to 
6274      *  interfere with the login process.
6275      */
6276     if (loggedOn) {
6277         if (!strncmp(message, "tellics ", 8)) {
6278             SendToICS(message + 8);
6279             SendToICS("\n");
6280             return;
6281         }
6282         if (!strncmp(message, "tellicsnoalias ", 15)) {
6283             SendToICS(ics_prefix);
6284             SendToICS(message + 15);
6285             SendToICS("\n");
6286             return;
6287         }
6288         /* The following are for backward compatibility only */
6289         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6290             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6291             SendToICS(ics_prefix);
6292             SendToICS(message);
6293             SendToICS("\n");
6294             return;
6295         }
6296     }
6297     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6298         return;
6299     }
6300     /*
6301      * If the move is illegal, cancel it and redraw the board.
6302      * Also deal with other error cases.  Matching is rather loose
6303      * here to accommodate engines written before the spec.
6304      */
6305     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6306         strncmp(message, "Error", 5) == 0) {
6307         if (StrStr(message, "name") || 
6308             StrStr(message, "rating") || StrStr(message, "?") ||
6309             StrStr(message, "result") || StrStr(message, "board") ||
6310             StrStr(message, "bk") || StrStr(message, "computer") ||
6311             StrStr(message, "variant") || StrStr(message, "hint") ||
6312             StrStr(message, "random") || StrStr(message, "depth") ||
6313             StrStr(message, "accepted")) {
6314             return;
6315         }
6316         if (StrStr(message, "protover")) {
6317           /* Program is responding to input, so it's apparently done
6318              initializing, and this error message indicates it is
6319              protocol version 1.  So we don't need to wait any longer
6320              for it to initialize and send feature commands. */
6321           FeatureDone(cps, 1);
6322           cps->protocolVersion = 1;
6323           return;
6324         }
6325         cps->maybeThinking = FALSE;
6326
6327         if (StrStr(message, "draw")) {
6328             /* Program doesn't have "draw" command */
6329             cps->sendDrawOffers = 0;
6330             return;
6331         }
6332         if (cps->sendTime != 1 &&
6333             (StrStr(message, "time") || StrStr(message, "otim"))) {
6334           /* Program apparently doesn't have "time" or "otim" command */
6335           cps->sendTime = 0;
6336           return;
6337         }
6338         if (StrStr(message, "analyze")) {
6339             cps->analysisSupport = FALSE;
6340             cps->analyzing = FALSE;
6341             Reset(FALSE, TRUE);
6342             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6343             DisplayError(buf2, 0);
6344             return;
6345         }
6346         if (StrStr(message, "(no matching move)st")) {
6347           /* Special kludge for GNU Chess 4 only */
6348           cps->stKludge = TRUE;
6349           SendTimeControl(cps, movesPerSession, timeControl,
6350                           timeIncrement, appData.searchDepth,
6351                           searchTime);
6352           return;
6353         }
6354         if (StrStr(message, "(no matching move)sd")) {
6355           /* Special kludge for GNU Chess 4 only */
6356           cps->sdKludge = TRUE;
6357           SendTimeControl(cps, movesPerSession, timeControl,
6358                           timeIncrement, appData.searchDepth,
6359                           searchTime);
6360           return;
6361         }
6362         if (!StrStr(message, "llegal")) {
6363             return;
6364         }
6365         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6366             gameMode == IcsIdle) return;
6367         if (forwardMostMove <= backwardMostMove) return;
6368 #if 0
6369         /* Following removed: it caused a bug where a real illegal move
6370            message in analyze mored would be ignored. */
6371         if (cps == &first && programStats.ok_to_send == 0) {
6372             /* Bogus message from Crafty responding to "."  This filtering
6373                can miss some of the bad messages, but fortunately the bug 
6374                is fixed in current Crafty versions, so it doesn't matter. */
6375             return;
6376         }
6377 #endif
6378         if (pausing) PauseEvent();
6379         if (gameMode == PlayFromGameFile) {
6380             /* Stop reading this game file */
6381             gameMode = EditGame;
6382             ModeHighlight();
6383         }
6384         currentMove = --forwardMostMove;
6385         DisplayMove(currentMove-1); /* before DisplayMoveError */
6386         SwitchClocks();
6387         DisplayBothClocks();
6388         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6389                 parseList[currentMove], cps->which);
6390         DisplayMoveError(buf1);
6391         DrawPosition(FALSE, boards[currentMove]);
6392
6393         /* [HGM] illegal-move claim should forfeit game when Xboard */
6394         /* only passes fully legal moves                            */
6395         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6396             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6397                                 "False illegal-move claim", GE_XBOARD );
6398         }
6399         return;
6400     }
6401     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6402         /* Program has a broken "time" command that
6403            outputs a string not ending in newline.
6404            Don't use it. */
6405         cps->sendTime = 0;
6406     }
6407     
6408     /*
6409      * If chess program startup fails, exit with an error message.
6410      * Attempts to recover here are futile.
6411      */
6412     if ((StrStr(message, "unknown host") != NULL)
6413         || (StrStr(message, "No remote directory") != NULL)
6414         || (StrStr(message, "not found") != NULL)
6415         || (StrStr(message, "No such file") != NULL)
6416         || (StrStr(message, "can't alloc") != NULL)
6417         || (StrStr(message, "Permission denied") != NULL)) {
6418
6419         cps->maybeThinking = FALSE;
6420         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6421                 cps->which, cps->program, cps->host, message);
6422         RemoveInputSource(cps->isr);
6423         DisplayFatalError(buf1, 0, 1);
6424         return;
6425     }
6426     
6427     /* 
6428      * Look for hint output
6429      */
6430     if (sscanf(message, "Hint: %s", buf1) == 1) {
6431         if (cps == &first && hintRequested) {
6432             hintRequested = FALSE;
6433             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6434                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6435                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6436                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6437                                     fromY, fromX, toY, toX, promoChar, buf1);
6438                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6439                 DisplayInformation(buf2);
6440             } else {
6441                 /* Hint move could not be parsed!? */
6442               snprintf(buf2, sizeof(buf2),
6443                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6444                         buf1, cps->which);
6445                 DisplayError(buf2, 0);
6446             }
6447         } else {
6448             strcpy(lastHint, buf1);
6449         }
6450         return;
6451     }
6452
6453     /*
6454      * Ignore other messages if game is not in progress
6455      */
6456     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6457         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6458
6459     /*
6460      * look for win, lose, draw, or draw offer
6461      */
6462     if (strncmp(message, "1-0", 3) == 0) {
6463         char *p, *q, *r = "";
6464         p = strchr(message, '{');
6465         if (p) {
6466             q = strchr(p, '}');
6467             if (q) {
6468                 *q = NULLCHAR;
6469                 r = p + 1;
6470             }
6471         }
6472         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6473         return;
6474     } else if (strncmp(message, "0-1", 3) == 0) {
6475         char *p, *q, *r = "";
6476         p = strchr(message, '{');
6477         if (p) {
6478             q = strchr(p, '}');
6479             if (q) {
6480                 *q = NULLCHAR;
6481                 r = p + 1;
6482             }
6483         }
6484         /* Kludge for Arasan 4.1 bug */
6485         if (strcmp(r, "Black resigns") == 0) {
6486             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6487             return;
6488         }
6489         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6490         return;
6491     } else if (strncmp(message, "1/2", 3) == 0) {
6492         char *p, *q, *r = "";
6493         p = strchr(message, '{');
6494         if (p) {
6495             q = strchr(p, '}');
6496             if (q) {
6497                 *q = NULLCHAR;
6498                 r = p + 1;
6499             }
6500         }
6501             
6502         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6503         return;
6504
6505     } else if (strncmp(message, "White resign", 12) == 0) {
6506         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6507         return;
6508     } else if (strncmp(message, "Black resign", 12) == 0) {
6509         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6510         return;
6511     } else if (strncmp(message, "White matches", 13) == 0 ||
6512                strncmp(message, "Black matches", 13) == 0   ) {
6513         /* [HGM] ignore GNUShogi noises */
6514         return;
6515     } else if (strncmp(message, "White", 5) == 0 &&
6516                message[5] != '(' &&
6517                StrStr(message, "Black") == NULL) {
6518         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6519         return;
6520     } else if (strncmp(message, "Black", 5) == 0 &&
6521                message[5] != '(') {
6522         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6523         return;
6524     } else if (strcmp(message, "resign") == 0 ||
6525                strcmp(message, "computer resigns") == 0) {
6526         switch (gameMode) {
6527           case MachinePlaysBlack:
6528           case IcsPlayingBlack:
6529             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6530             break;
6531           case MachinePlaysWhite:
6532           case IcsPlayingWhite:
6533             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6534             break;
6535           case TwoMachinesPlay:
6536             if (cps->twoMachinesColor[0] == 'w')
6537               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6538             else
6539               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6540             break;
6541           default:
6542             /* can't happen */
6543             break;
6544         }
6545         return;
6546     } else if (strncmp(message, "opponent mates", 14) == 0) {
6547         switch (gameMode) {
6548           case MachinePlaysBlack:
6549           case IcsPlayingBlack:
6550             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6551             break;
6552           case MachinePlaysWhite:
6553           case IcsPlayingWhite:
6554             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6555             break;
6556           case TwoMachinesPlay:
6557             if (cps->twoMachinesColor[0] == 'w')
6558               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6559             else
6560               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6561             break;
6562           default:
6563             /* can't happen */
6564             break;
6565         }
6566         return;
6567     } else if (strncmp(message, "computer mates", 14) == 0) {
6568         switch (gameMode) {
6569           case MachinePlaysBlack:
6570           case IcsPlayingBlack:
6571             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6572             break;
6573           case MachinePlaysWhite:
6574           case IcsPlayingWhite:
6575             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6576             break;
6577           case TwoMachinesPlay:
6578             if (cps->twoMachinesColor[0] == 'w')
6579               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6580             else
6581               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6582             break;
6583           default:
6584             /* can't happen */
6585             break;
6586         }
6587         return;
6588     } else if (strncmp(message, "checkmate", 9) == 0) {
6589         if (WhiteOnMove(forwardMostMove)) {
6590             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6591         } else {
6592             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6593         }
6594         return;
6595     } else if (strstr(message, "Draw") != NULL ||
6596                strstr(message, "game is a draw") != NULL) {
6597         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6598         return;
6599     } else if (strstr(message, "offer") != NULL &&
6600                strstr(message, "draw") != NULL) {
6601 #if ZIPPY
6602         if (appData.zippyPlay && first.initDone) {
6603             /* Relay offer to ICS */
6604             SendToICS(ics_prefix);
6605             SendToICS("draw\n");
6606         }
6607 #endif
6608         cps->offeredDraw = 2; /* valid until this engine moves twice */
6609         if (gameMode == TwoMachinesPlay) {
6610             if (cps->other->offeredDraw) {
6611                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6612             /* [HGM] in two-machine mode we delay relaying draw offer      */
6613             /* until after we also have move, to see if it is really claim */
6614             }
6615 #if 0
6616               else {
6617                 if (cps->other->sendDrawOffers) {
6618                     SendToProgram("draw\n", cps->other);
6619                 }
6620             }
6621 #endif
6622         } else if (gameMode == MachinePlaysWhite ||
6623                    gameMode == MachinePlaysBlack) {
6624           if (userOfferedDraw) {
6625             DisplayInformation(_("Machine accepts your draw offer"));
6626             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6627           } else {
6628             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6629           }
6630         }
6631     }
6632
6633     
6634     /*
6635      * Look for thinking output
6636      */
6637     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6638           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6639                                 ) {
6640         int plylev, mvleft, mvtot, curscore, time;
6641         char mvname[MOVE_LEN];
6642         u64 nodes; // [DM]
6643         char plyext;
6644         int ignore = FALSE;
6645         int prefixHint = FALSE;
6646         mvname[0] = NULLCHAR;
6647
6648         switch (gameMode) {
6649           case MachinePlaysBlack:
6650           case IcsPlayingBlack:
6651             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6652             break;
6653           case MachinePlaysWhite:
6654           case IcsPlayingWhite:
6655             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6656             break;
6657           case AnalyzeMode:
6658           case AnalyzeFile:
6659             break;
6660           case IcsObserving: /* [DM] icsEngineAnalyze */
6661             if (!appData.icsEngineAnalyze) ignore = TRUE;
6662             break;
6663           case TwoMachinesPlay:
6664             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6665                 ignore = TRUE;
6666             }
6667             break;
6668           default:
6669             ignore = TRUE;
6670             break;
6671         }
6672
6673         if (!ignore) {
6674             buf1[0] = NULLCHAR;
6675             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6676                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6677
6678                 if (plyext != ' ' && plyext != '\t') {
6679                     time *= 100;
6680                 }
6681
6682                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6683                 if( cps->scoreIsAbsolute && 
6684                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6685                 {
6686                     curscore = -curscore;
6687                 }
6688
6689
6690                 programStats.depth = plylev;
6691                 programStats.nodes = nodes;
6692                 programStats.time = time;
6693                 programStats.score = curscore;
6694                 programStats.got_only_move = 0;
6695
6696                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6697                         int ticklen;
6698
6699                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6700                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6701                         if(WhiteOnMove(forwardMostMove)) 
6702                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6703                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6704                 }
6705
6706                 /* Buffer overflow protection */
6707                 if (buf1[0] != NULLCHAR) {
6708                     if (strlen(buf1) >= sizeof(programStats.movelist)
6709                         && appData.debugMode) {
6710                         fprintf(debugFP,
6711                                 "PV is too long; using the first %d bytes.\n",
6712                                 sizeof(programStats.movelist) - 1);
6713                     }
6714
6715                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6716                 } else {
6717                     sprintf(programStats.movelist, " no PV\n");
6718                 }
6719
6720                 if (programStats.seen_stat) {
6721                     programStats.ok_to_send = 1;
6722                 }
6723
6724                 if (strchr(programStats.movelist, '(') != NULL) {
6725                     programStats.line_is_book = 1;
6726                     programStats.nr_moves = 0;
6727                     programStats.moves_left = 0;
6728                 } else {
6729                     programStats.line_is_book = 0;
6730                 }
6731
6732                 SendProgramStatsToFrontend( cps, &programStats );
6733
6734                 /* 
6735                     [AS] Protect the thinkOutput buffer from overflow... this
6736                     is only useful if buf1 hasn't overflowed first!
6737                 */
6738                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6739                         plylev, 
6740                         (gameMode == TwoMachinesPlay ?
6741                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6742                         ((double) curscore) / 100.0,
6743                         prefixHint ? lastHint : "",
6744                         prefixHint ? " " : "" );
6745
6746                 if( buf1[0] != NULLCHAR ) {
6747                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6748
6749                     if( strlen(buf1) > max_len ) {
6750                         if( appData.debugMode) {
6751                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6752                         }
6753                         buf1[max_len+1] = '\0';
6754                     }
6755
6756                     strcat( thinkOutput, buf1 );
6757                 }
6758
6759                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6760                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6761                     DisplayMove(currentMove - 1);
6762                     DisplayAnalysis();
6763                 }
6764                 return;
6765
6766             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6767                 /* crafty (9.25+) says "(only move) <move>"
6768                  * if there is only 1 legal move
6769                  */
6770                 sscanf(p, "(only move) %s", buf1);
6771                 sprintf(thinkOutput, "%s (only move)", buf1);
6772                 sprintf(programStats.movelist, "%s (only move)", buf1);
6773                 programStats.depth = 1;
6774                 programStats.nr_moves = 1;
6775                 programStats.moves_left = 1;
6776                 programStats.nodes = 1;
6777                 programStats.time = 1;
6778                 programStats.got_only_move = 1;
6779
6780                 /* Not really, but we also use this member to
6781                    mean "line isn't going to change" (Crafty
6782                    isn't searching, so stats won't change) */
6783                 programStats.line_is_book = 1;
6784
6785                 SendProgramStatsToFrontend( cps, &programStats );
6786                 
6787                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
6788                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6789                     DisplayMove(currentMove - 1);
6790                     DisplayAnalysis();
6791                 }
6792                 return;
6793             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6794                               &time, &nodes, &plylev, &mvleft,
6795                               &mvtot, mvname) >= 5) {
6796                 /* The stat01: line is from Crafty (9.29+) in response
6797                    to the "." command */
6798                 programStats.seen_stat = 1;
6799                 cps->maybeThinking = TRUE;
6800
6801                 if (programStats.got_only_move || !appData.periodicUpdates)
6802                   return;
6803
6804                 programStats.depth = plylev;
6805                 programStats.time = time;
6806                 programStats.nodes = nodes;
6807                 programStats.moves_left = mvleft;
6808                 programStats.nr_moves = mvtot;
6809                 strcpy(programStats.move_name, mvname);
6810                 programStats.ok_to_send = 1;
6811                 programStats.movelist[0] = '\0';
6812
6813                 SendProgramStatsToFrontend( cps, &programStats );
6814
6815                 DisplayAnalysis();
6816                 return;
6817
6818             } else if (strncmp(message,"++",2) == 0) {
6819                 /* Crafty 9.29+ outputs this */
6820                 programStats.got_fail = 2;
6821                 return;
6822
6823             } else if (strncmp(message,"--",2) == 0) {
6824                 /* Crafty 9.29+ outputs this */
6825                 programStats.got_fail = 1;
6826                 return;
6827
6828             } else if (thinkOutput[0] != NULLCHAR &&
6829                        strncmp(message, "    ", 4) == 0) {
6830                 unsigned message_len;
6831
6832                 p = message;
6833                 while (*p && *p == ' ') p++;
6834
6835                 message_len = strlen( p );
6836
6837                 /* [AS] Avoid buffer overflow */
6838                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6839                     strcat(thinkOutput, " ");
6840                     strcat(thinkOutput, p);
6841                 }
6842
6843                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6844                     strcat(programStats.movelist, " ");
6845                     strcat(programStats.movelist, p);
6846                 }
6847
6848                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6849                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6850                     DisplayMove(currentMove - 1);
6851                     DisplayAnalysis();
6852                 }
6853                 return;
6854             }
6855         }
6856         else {
6857             buf1[0] = NULLCHAR;
6858
6859             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6860                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
6861             {
6862                 ChessProgramStats cpstats;
6863
6864                 if (plyext != ' ' && plyext != '\t') {
6865                     time *= 100;
6866                 }
6867
6868                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6869                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6870                     curscore = -curscore;
6871                 }
6872
6873                 cpstats.depth = plylev;
6874                 cpstats.nodes = nodes;
6875                 cpstats.time = time;
6876                 cpstats.score = curscore;
6877                 cpstats.got_only_move = 0;
6878                 cpstats.movelist[0] = '\0';
6879
6880                 if (buf1[0] != NULLCHAR) {
6881                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6882                 }
6883
6884                 cpstats.ok_to_send = 0;
6885                 cpstats.line_is_book = 0;
6886                 cpstats.nr_moves = 0;
6887                 cpstats.moves_left = 0;
6888
6889                 SendProgramStatsToFrontend( cps, &cpstats );
6890             }
6891         }
6892     }
6893 }
6894
6895
6896 /* Parse a game score from the character string "game", and
6897    record it as the history of the current game.  The game
6898    score is NOT assumed to start from the standard position. 
6899    The display is not updated in any way.
6900    */
6901 void
6902 ParseGameHistory(game)
6903      char *game;
6904 {
6905     ChessMove moveType;
6906     int fromX, fromY, toX, toY, boardIndex;
6907     char promoChar;
6908     char *p, *q;
6909     char buf[MSG_SIZ];
6910
6911     if (appData.debugMode)
6912       fprintf(debugFP, "Parsing game history: %s\n", game);
6913
6914     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6915     gameInfo.site = StrSave(appData.icsHost);
6916     gameInfo.date = PGNDate();
6917     gameInfo.round = StrSave("-");
6918
6919     /* Parse out names of players */
6920     while (*game == ' ') game++;
6921     p = buf;
6922     while (*game != ' ') *p++ = *game++;
6923     *p = NULLCHAR;
6924     gameInfo.white = StrSave(buf);
6925     while (*game == ' ') game++;
6926     p = buf;
6927     while (*game != ' ' && *game != '\n') *p++ = *game++;
6928     *p = NULLCHAR;
6929     gameInfo.black = StrSave(buf);
6930
6931     /* Parse moves */
6932     boardIndex = blackPlaysFirst ? 1 : 0;
6933     yynewstr(game);
6934     for (;;) {
6935         yyboardindex = boardIndex;
6936         moveType = (ChessMove) yylex();
6937         switch (moveType) {
6938           case IllegalMove:             /* maybe suicide chess, etc. */
6939   if (appData.debugMode) {
6940     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6941     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6942     setbuf(debugFP, NULL);
6943   }
6944           case WhitePromotionChancellor:
6945           case BlackPromotionChancellor:
6946           case WhitePromotionArchbishop:
6947           case BlackPromotionArchbishop:
6948           case WhitePromotionQueen:
6949           case BlackPromotionQueen:
6950           case WhitePromotionRook:
6951           case BlackPromotionRook:
6952           case WhitePromotionBishop:
6953           case BlackPromotionBishop:
6954           case WhitePromotionKnight:
6955           case BlackPromotionKnight:
6956           case WhitePromotionKing:
6957           case BlackPromotionKing:
6958           case NormalMove:
6959           case WhiteCapturesEnPassant:
6960           case BlackCapturesEnPassant:
6961           case WhiteKingSideCastle:
6962           case WhiteQueenSideCastle:
6963           case BlackKingSideCastle:
6964           case BlackQueenSideCastle:
6965           case WhiteKingSideCastleWild:
6966           case WhiteQueenSideCastleWild:
6967           case BlackKingSideCastleWild:
6968           case BlackQueenSideCastleWild:
6969           /* PUSH Fabien */
6970           case WhiteHSideCastleFR:
6971           case WhiteASideCastleFR:
6972           case BlackHSideCastleFR:
6973           case BlackASideCastleFR:
6974           /* POP Fabien */
6975             fromX = currentMoveString[0] - AAA;
6976             fromY = currentMoveString[1] - ONE;
6977             toX = currentMoveString[2] - AAA;
6978             toY = currentMoveString[3] - ONE;
6979             promoChar = currentMoveString[4];
6980             break;
6981           case WhiteDrop:
6982           case BlackDrop:
6983             fromX = moveType == WhiteDrop ?
6984               (int) CharToPiece(ToUpper(currentMoveString[0])) :
6985             (int) CharToPiece(ToLower(currentMoveString[0]));
6986             fromY = DROP_RANK;
6987             toX = currentMoveString[2] - AAA;
6988             toY = currentMoveString[3] - ONE;
6989             promoChar = NULLCHAR;
6990             break;
6991           case AmbiguousMove:
6992             /* bug? */
6993             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6994   if (appData.debugMode) {
6995     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6996     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6997     setbuf(debugFP, NULL);
6998   }
6999             DisplayError(buf, 0);
7000             return;
7001           case ImpossibleMove:
7002             /* bug? */
7003             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7004   if (appData.debugMode) {
7005     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7006     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7007     setbuf(debugFP, NULL);
7008   }
7009             DisplayError(buf, 0);
7010             return;
7011           case (ChessMove) 0:   /* end of file */
7012             if (boardIndex < backwardMostMove) {
7013                 /* Oops, gap.  How did that happen? */
7014                 DisplayError(_("Gap in move list"), 0);
7015                 return;
7016             }
7017             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7018             if (boardIndex > forwardMostMove) {
7019                 forwardMostMove = boardIndex;
7020             }
7021             return;
7022           case ElapsedTime:
7023             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7024                 strcat(parseList[boardIndex-1], " ");
7025                 strcat(parseList[boardIndex-1], yy_text);
7026             }
7027             continue;
7028           case Comment:
7029           case PGNTag:
7030           case NAG:
7031           default:
7032             /* ignore */
7033             continue;
7034           case WhiteWins:
7035           case BlackWins:
7036           case GameIsDrawn:
7037           case GameUnfinished:
7038             if (gameMode == IcsExamining) {
7039                 if (boardIndex < backwardMostMove) {
7040                     /* Oops, gap.  How did that happen? */
7041                     return;
7042                 }
7043                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7044                 return;
7045             }
7046             gameInfo.result = moveType;
7047             p = strchr(yy_text, '{');
7048             if (p == NULL) p = strchr(yy_text, '(');
7049             if (p == NULL) {
7050                 p = yy_text;
7051                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7052             } else {
7053                 q = strchr(p, *p == '{' ? '}' : ')');
7054                 if (q != NULL) *q = NULLCHAR;
7055                 p++;
7056             }
7057             gameInfo.resultDetails = StrSave(p);
7058             continue;
7059         }
7060         if (boardIndex >= forwardMostMove &&
7061             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7062             backwardMostMove = blackPlaysFirst ? 1 : 0;
7063             return;
7064         }
7065         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7066                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7067                                  parseList[boardIndex]);
7068         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7069         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7070         /* currentMoveString is set as a side-effect of yylex */
7071         strcpy(moveList[boardIndex], currentMoveString);
7072         strcat(moveList[boardIndex], "\n");
7073         boardIndex++;
7074         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7075                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7076         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7077                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7078           case MT_NONE:
7079           case MT_STALEMATE:
7080           default:
7081             break;
7082           case MT_CHECK:
7083             if(gameInfo.variant != VariantShogi)
7084                 strcat(parseList[boardIndex - 1], "+");
7085             break;
7086           case MT_CHECKMATE:
7087           case MT_STAINMATE:
7088             strcat(parseList[boardIndex - 1], "#");
7089             break;
7090         }
7091     }
7092 }
7093
7094
7095 /* Apply a move to the given board  */
7096 void
7097 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7098      int fromX, fromY, toX, toY;
7099      int promoChar;
7100      Board board;
7101      char *castling;
7102      char *ep;
7103 {
7104   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7105
7106     /* [HGM] compute & store e.p. status and castling rights for new position */
7107     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7108     { int i;
7109
7110       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7111       oldEP = *ep;
7112       *ep = EP_NONE;
7113
7114       if( board[toY][toX] != EmptySquare ) 
7115            *ep = EP_CAPTURE;  
7116
7117       if( board[fromY][fromX] == WhitePawn ) {
7118            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7119                *ep = EP_PAWN_MOVE;
7120            if( toY-fromY==2) {
7121                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7122                         gameInfo.variant != VariantBerolina || toX < fromX)
7123                       *ep = toX | berolina;
7124                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7125                         gameInfo.variant != VariantBerolina || toX > fromX) 
7126                       *ep = toX;
7127            }
7128       } else 
7129       if( board[fromY][fromX] == BlackPawn ) {
7130            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7131                *ep = EP_PAWN_MOVE; 
7132            if( toY-fromY== -2) {
7133                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7134                         gameInfo.variant != VariantBerolina || toX < fromX)
7135                       *ep = toX | berolina;
7136                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7137                         gameInfo.variant != VariantBerolina || toX > fromX) 
7138                       *ep = toX;
7139            }
7140        }
7141
7142        for(i=0; i<nrCastlingRights; i++) {
7143            if(castling[i] == fromX && castlingRank[i] == fromY ||
7144               castling[i] == toX   && castlingRank[i] == toY   
7145              ) castling[i] = -1; // revoke for moved or captured piece
7146        }
7147
7148     }
7149
7150   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7151   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7152        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7153          
7154   if (fromX == toX && fromY == toY) return;
7155
7156   if (fromY == DROP_RANK) {
7157         /* must be first */
7158         piece = board[toY][toX] = (ChessSquare) fromX;
7159   } else {
7160      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7161      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7162      if(gameInfo.variant == VariantKnightmate)
7163          king += (int) WhiteUnicorn - (int) WhiteKing;
7164
7165     /* Code added by Tord: */
7166     /* FRC castling assumed when king captures friendly rook. */
7167     if (board[fromY][fromX] == WhiteKing &&
7168              board[toY][toX] == WhiteRook) {
7169       board[fromY][fromX] = EmptySquare;
7170       board[toY][toX] = EmptySquare;
7171       if(toX > fromX) {
7172         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7173       } else {
7174         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7175       }
7176     } else if (board[fromY][fromX] == BlackKing &&
7177                board[toY][toX] == BlackRook) {
7178       board[fromY][fromX] = EmptySquare;
7179       board[toY][toX] = EmptySquare;
7180       if(toX > fromX) {
7181         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7182       } else {
7183         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7184       }
7185     /* End of code added by Tord */
7186
7187     } else if (board[fromY][fromX] == king
7188         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7189         && toY == fromY && toX > fromX+1) {
7190         board[fromY][fromX] = EmptySquare;
7191         board[toY][toX] = king;
7192         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7193         board[fromY][BOARD_RGHT-1] = EmptySquare;
7194     } else if (board[fromY][fromX] == king
7195         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7196                && toY == fromY && toX < fromX-1) {
7197         board[fromY][fromX] = EmptySquare;
7198         board[toY][toX] = king;
7199         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7200         board[fromY][BOARD_LEFT] = EmptySquare;
7201     } else if (board[fromY][fromX] == WhitePawn
7202                && toY == BOARD_HEIGHT-1
7203                && gameInfo.variant != VariantXiangqi
7204                ) {
7205         /* white pawn promotion */
7206         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7207         if (board[toY][toX] == EmptySquare) {
7208             board[toY][toX] = WhiteQueen;
7209         }
7210         if(gameInfo.variant==VariantBughouse ||
7211            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7212             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7213         board[fromY][fromX] = EmptySquare;
7214     } else if ((fromY == BOARD_HEIGHT-4)
7215                && (toX != fromX)
7216                && gameInfo.variant != VariantXiangqi
7217                && gameInfo.variant != VariantBerolina
7218                && (board[fromY][fromX] == WhitePawn)
7219                && (board[toY][toX] == EmptySquare)) {
7220         board[fromY][fromX] = EmptySquare;
7221         board[toY][toX] = WhitePawn;
7222         captured = board[toY - 1][toX];
7223         board[toY - 1][toX] = EmptySquare;
7224     } else if ((fromY == BOARD_HEIGHT-4)
7225                && (toX == fromX)
7226                && gameInfo.variant == VariantBerolina
7227                && (board[fromY][fromX] == WhitePawn)
7228                && (board[toY][toX] == EmptySquare)) {
7229         board[fromY][fromX] = EmptySquare;
7230         board[toY][toX] = WhitePawn;
7231         if(oldEP & EP_BEROLIN_A) {
7232                 captured = board[fromY][fromX-1];
7233                 board[fromY][fromX-1] = EmptySquare;
7234         }else{  captured = board[fromY][fromX+1];
7235                 board[fromY][fromX+1] = EmptySquare;
7236         }
7237     } else if (board[fromY][fromX] == king
7238         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7239                && toY == fromY && toX > fromX+1) {
7240         board[fromY][fromX] = EmptySquare;
7241         board[toY][toX] = king;
7242         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7243         board[fromY][BOARD_RGHT-1] = EmptySquare;
7244     } else if (board[fromY][fromX] == king
7245         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7246                && toY == fromY && toX < fromX-1) {
7247         board[fromY][fromX] = EmptySquare;
7248         board[toY][toX] = king;
7249         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7250         board[fromY][BOARD_LEFT] = EmptySquare;
7251     } else if (fromY == 7 && fromX == 3
7252                && board[fromY][fromX] == BlackKing
7253                && toY == 7 && toX == 5) {
7254         board[fromY][fromX] = EmptySquare;
7255         board[toY][toX] = BlackKing;
7256         board[fromY][7] = EmptySquare;
7257         board[toY][4] = BlackRook;
7258     } else if (fromY == 7 && fromX == 3
7259                && board[fromY][fromX] == BlackKing
7260                && toY == 7 && toX == 1) {
7261         board[fromY][fromX] = EmptySquare;
7262         board[toY][toX] = BlackKing;
7263         board[fromY][0] = EmptySquare;
7264         board[toY][2] = BlackRook;
7265     } else if (board[fromY][fromX] == BlackPawn
7266                && toY == 0
7267                && gameInfo.variant != VariantXiangqi
7268                ) {
7269         /* black pawn promotion */
7270         board[0][toX] = CharToPiece(ToLower(promoChar));
7271         if (board[0][toX] == EmptySquare) {
7272             board[0][toX] = BlackQueen;
7273         }
7274         if(gameInfo.variant==VariantBughouse ||
7275            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7276             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7277         board[fromY][fromX] = EmptySquare;
7278     } else if ((fromY == 3)
7279                && (toX != fromX)
7280                && gameInfo.variant != VariantXiangqi
7281                && gameInfo.variant != VariantBerolina
7282                && (board[fromY][fromX] == BlackPawn)
7283                && (board[toY][toX] == EmptySquare)) {
7284         board[fromY][fromX] = EmptySquare;
7285         board[toY][toX] = BlackPawn;
7286         captured = board[toY + 1][toX];
7287         board[toY + 1][toX] = EmptySquare;
7288     } else if ((fromY == 3)
7289                && (toX == fromX)
7290                && gameInfo.variant == VariantBerolina
7291                && (board[fromY][fromX] == BlackPawn)
7292                && (board[toY][toX] == EmptySquare)) {
7293         board[fromY][fromX] = EmptySquare;
7294         board[toY][toX] = BlackPawn;
7295         if(oldEP & EP_BEROLIN_A) {
7296                 captured = board[fromY][fromX-1];
7297                 board[fromY][fromX-1] = EmptySquare;
7298         }else{  captured = board[fromY][fromX+1];
7299                 board[fromY][fromX+1] = EmptySquare;
7300         }
7301     } else {
7302         board[toY][toX] = board[fromY][fromX];
7303         board[fromY][fromX] = EmptySquare;
7304     }
7305
7306     /* [HGM] now we promote for Shogi, if needed */
7307     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7308         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7309   }
7310
7311     if (gameInfo.holdingsWidth != 0) {
7312
7313       /* !!A lot more code needs to be written to support holdings  */
7314       /* [HGM] OK, so I have written it. Holdings are stored in the */
7315       /* penultimate board files, so they are automaticlly stored   */
7316       /* in the game history.                                       */
7317       if (fromY == DROP_RANK) {
7318         /* Delete from holdings, by decreasing count */
7319         /* and erasing image if necessary            */
7320         p = (int) fromX;
7321         if(p < (int) BlackPawn) { /* white drop */
7322              p -= (int)WhitePawn;
7323              if(p >= gameInfo.holdingsSize) p = 0;
7324              if(--board[p][BOARD_WIDTH-2] == 0)
7325                   board[p][BOARD_WIDTH-1] = EmptySquare;
7326         } else {                  /* black drop */
7327              p -= (int)BlackPawn;
7328              if(p >= gameInfo.holdingsSize) p = 0;
7329              if(--board[BOARD_HEIGHT-1-p][1] == 0)
7330                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7331         }
7332       }
7333       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7334           && gameInfo.variant != VariantBughouse        ) {
7335         /* [HGM] holdings: Add to holdings, if holdings exist */
7336         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7337                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7338                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7339         }
7340         p = (int) captured;
7341         if (p >= (int) BlackPawn) {
7342           p -= (int)BlackPawn;
7343           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7344                   /* in Shogi restore piece to its original  first */
7345                   captured = (ChessSquare) (DEMOTED captured);
7346                   p = DEMOTED p;
7347           }
7348           p = PieceToNumber((ChessSquare)p);
7349           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7350           board[p][BOARD_WIDTH-2]++;
7351           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7352         } else {
7353           p -= (int)WhitePawn;
7354           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7355                   captured = (ChessSquare) (DEMOTED captured);
7356                   p = DEMOTED p;
7357           }
7358           p = PieceToNumber((ChessSquare)p);
7359           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7360           board[BOARD_HEIGHT-1-p][1]++;
7361           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7362         }
7363       }
7364
7365     } else if (gameInfo.variant == VariantAtomic) {
7366       if (captured != EmptySquare) {
7367         int y, x;
7368         for (y = toY-1; y <= toY+1; y++) {
7369           for (x = toX-1; x <= toX+1; x++) {
7370             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7371                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7372               board[y][x] = EmptySquare;
7373             }
7374           }
7375         }
7376         board[toY][toX] = EmptySquare;
7377       }
7378     }
7379     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7380         /* [HGM] Shogi promotions */
7381         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7382     }
7383
7384     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7385                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7386         // [HGM] superchess: take promotion piece out of holdings
7387         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7388         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7389             if(!--board[k][BOARD_WIDTH-2])
7390                 board[k][BOARD_WIDTH-1] = EmptySquare;
7391         } else {
7392             if(!--board[BOARD_HEIGHT-1-k][1])
7393                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7394         }
7395     }
7396
7397 }
7398
7399 /* Updates forwardMostMove */
7400 void
7401 MakeMove(fromX, fromY, toX, toY, promoChar)
7402      int fromX, fromY, toX, toY;
7403      int promoChar;
7404 {
7405 //    forwardMostMove++; // [HGM] bare: moved downstream
7406
7407     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7408         int timeLeft; static int lastLoadFlag=0; int king, piece;
7409         piece = boards[forwardMostMove][fromY][fromX];
7410         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7411         if(gameInfo.variant == VariantKnightmate)
7412             king += (int) WhiteUnicorn - (int) WhiteKing;
7413         if(forwardMostMove == 0) {
7414             if(blackPlaysFirst) 
7415                 fprintf(serverMoves, "%s;", second.tidy);
7416             fprintf(serverMoves, "%s;", first.tidy);
7417             if(!blackPlaysFirst) 
7418                 fprintf(serverMoves, "%s;", second.tidy);
7419         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7420         lastLoadFlag = loadFlag;
7421         // print base move
7422         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7423         // print castling suffix
7424         if( toY == fromY && piece == king ) {
7425             if(toX-fromX > 1)
7426                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7427             if(fromX-toX >1)
7428                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7429         }
7430         // e.p. suffix
7431         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7432              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7433              boards[forwardMostMove][toY][toX] == EmptySquare
7434              && fromX != toX )
7435                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7436         // promotion suffix
7437         if(promoChar != NULLCHAR)
7438                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7439         if(!loadFlag) {
7440             fprintf(serverMoves, "/%d/%d",
7441                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7442             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7443             else                      timeLeft = blackTimeRemaining/1000;
7444             fprintf(serverMoves, "/%d", timeLeft);
7445         }
7446         fflush(serverMoves);
7447     }
7448
7449     if (forwardMostMove+1 >= MAX_MOVES) {
7450       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7451                         0, 1);
7452       return;
7453     }
7454     SwitchClocks();
7455     timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
7456     timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
7457     if (commentList[forwardMostMove+1] != NULL) {
7458         free(commentList[forwardMostMove+1]);
7459         commentList[forwardMostMove+1] = NULL;
7460     }
7461     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7462     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7463     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7464                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7465     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7466     gameInfo.result = GameUnfinished;
7467     if (gameInfo.resultDetails != NULL) {
7468         free(gameInfo.resultDetails);
7469         gameInfo.resultDetails = NULL;
7470     }
7471     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7472                               moveList[forwardMostMove - 1]);
7473     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7474                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7475                              fromY, fromX, toY, toX, promoChar,
7476                              parseList[forwardMostMove - 1]);
7477     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7478                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7479                             castlingRights[forwardMostMove]) ) {
7480       case MT_NONE:
7481       case MT_STALEMATE:
7482       default:
7483         break;
7484       case MT_CHECK:
7485         if(gameInfo.variant != VariantShogi)
7486             strcat(parseList[forwardMostMove - 1], "+");
7487         break;
7488       case MT_CHECKMATE:
7489       case MT_STAINMATE:
7490         strcat(parseList[forwardMostMove - 1], "#");
7491         break;
7492     }
7493     if (appData.debugMode) {
7494         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7495     }
7496
7497 }
7498
7499 /* Updates currentMove if not pausing */
7500 void
7501 ShowMove(fromX, fromY, toX, toY)
7502 {
7503     int instant = (gameMode == PlayFromGameFile) ?
7504         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7505     if(appData.noGUI) return;
7506     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7507         if (!instant) {
7508             if (forwardMostMove == currentMove + 1) {
7509                 AnimateMove(boards[forwardMostMove - 1],
7510                             fromX, fromY, toX, toY);
7511             }
7512             if (appData.highlightLastMove) {
7513                 SetHighlights(fromX, fromY, toX, toY);
7514             }
7515         }
7516         currentMove = forwardMostMove;
7517     }
7518
7519     if (instant) return;
7520
7521     DisplayMove(currentMove - 1);
7522     DrawPosition(FALSE, boards[currentMove]);
7523     DisplayBothClocks();
7524     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7525 }
7526
7527 void SendEgtPath(ChessProgramState *cps)
7528 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7529         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7530
7531         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7532
7533         while(*p) {
7534             char c, *q = name+1, *r, *s;
7535
7536             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7537             while(*p && *p != ',') *q++ = *p++;
7538             *q++ = ':'; *q = 0;
7539             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7540                 strcmp(name, ",nalimov:") == 0 ) {
7541                 // take nalimov path from the menu-changeable option first, if it is defined
7542                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7543                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7544             } else
7545             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7546                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7547                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7548                 s = r = StrStr(s, ":") + 1; // beginning of path info
7549                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7550                 c = *r; *r = 0;             // temporarily null-terminate path info
7551                     *--q = 0;               // strip of trailig ':' from name
7552                     sprintf(buf, "egtbpath %s %s\n", name+1, s);
7553                 *r = c;
7554                 SendToProgram(buf,cps);     // send egtbpath command for this format
7555             }
7556             if(*p == ',') p++; // read away comma to position for next format name
7557         }
7558 }
7559
7560 void
7561 InitChessProgram(cps, setup)
7562      ChessProgramState *cps;
7563      int setup; /* [HGM] needed to setup FRC opening position */
7564 {
7565     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7566     if (appData.noChessProgram) return;
7567     hintRequested = FALSE;
7568     bookRequested = FALSE;
7569
7570     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7571     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7572     if(cps->memSize) { /* [HGM] memory */
7573         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7574         SendToProgram(buf, cps);
7575     }
7576     SendEgtPath(cps); /* [HGM] EGT */
7577     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7578         sprintf(buf, "cores %d\n", appData.smpCores);
7579         SendToProgram(buf, cps);
7580     }
7581
7582     SendToProgram(cps->initString, cps);
7583     if (gameInfo.variant != VariantNormal &&
7584         gameInfo.variant != VariantLoadable
7585         /* [HGM] also send variant if board size non-standard */
7586         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7587                                             ) {
7588       char *v = VariantName(gameInfo.variant);
7589       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7590         /* [HGM] in protocol 1 we have to assume all variants valid */
7591         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7592         DisplayFatalError(buf, 0, 1);
7593         return;
7594       }
7595
7596       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7597       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7598       if( gameInfo.variant == VariantXiangqi )
7599            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7600       if( gameInfo.variant == VariantShogi )
7601            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7602       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7603            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7604       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7605                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7606            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7607       if( gameInfo.variant == VariantCourier )
7608            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7609       if( gameInfo.variant == VariantSuper )
7610            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7611       if( gameInfo.variant == VariantGreat )
7612            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7613
7614       if(overruled) {
7615            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7616                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7617            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7618            if(StrStr(cps->variants, b) == NULL) { 
7619                // specific sized variant not known, check if general sizing allowed
7620                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7621                    if(StrStr(cps->variants, "boardsize") == NULL) {
7622                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7623                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7624                        DisplayFatalError(buf, 0, 1);
7625                        return;
7626                    }
7627                    /* [HGM] here we really should compare with the maximum supported board size */
7628                }
7629            }
7630       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7631       sprintf(buf, "variant %s\n", b);
7632       SendToProgram(buf, cps);
7633     }
7634     currentlyInitializedVariant = gameInfo.variant;
7635
7636     /* [HGM] send opening position in FRC to first engine */
7637     if(setup) {
7638           SendToProgram("force\n", cps);
7639           SendBoard(cps, 0);
7640           /* engine is now in force mode! Set flag to wake it up after first move. */
7641           setboardSpoiledMachineBlack = 1;
7642     }
7643
7644     if (cps->sendICS) {
7645       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7646       SendToProgram(buf, cps);
7647     }
7648     cps->maybeThinking = FALSE;
7649     cps->offeredDraw = 0;
7650     if (!appData.icsActive) {
7651         SendTimeControl(cps, movesPerSession, timeControl,
7652                         timeIncrement, appData.searchDepth,
7653                         searchTime);
7654     }
7655     if (appData.showThinking 
7656         // [HGM] thinking: four options require thinking output to be sent
7657         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7658                                 ) {
7659         SendToProgram("post\n", cps);
7660     }
7661     SendToProgram("hard\n", cps);
7662     if (!appData.ponderNextMove) {
7663         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7664            it without being sure what state we are in first.  "hard"
7665            is not a toggle, so that one is OK.
7666          */
7667         SendToProgram("easy\n", cps);
7668     }
7669     if (cps->usePing) {
7670       sprintf(buf, "ping %d\n", ++cps->lastPing);
7671       SendToProgram(buf, cps);
7672     }
7673     cps->initDone = TRUE;
7674 }   
7675
7676
7677 void
7678 StartChessProgram(cps)
7679      ChessProgramState *cps;
7680 {
7681     char buf[MSG_SIZ];
7682     int err;
7683
7684     if (appData.noChessProgram) return;
7685     cps->initDone = FALSE;
7686
7687     if (strcmp(cps->host, "localhost") == 0) {
7688         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7689     } else if (*appData.remoteShell == NULLCHAR) {
7690         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7691     } else {
7692         if (*appData.remoteUser == NULLCHAR) {
7693           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7694                     cps->program);
7695         } else {
7696           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7697                     cps->host, appData.remoteUser, cps->program);
7698         }
7699         err = StartChildProcess(buf, "", &cps->pr);
7700     }
7701     
7702     if (err != 0) {
7703         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7704         DisplayFatalError(buf, err, 1);
7705         cps->pr = NoProc;
7706         cps->isr = NULL;
7707         return;
7708     }
7709     
7710     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7711     if (cps->protocolVersion > 1) {
7712       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7713       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7714       cps->comboCnt = 0;  //                and values of combo boxes
7715       SendToProgram(buf, cps);
7716     } else {
7717       SendToProgram("xboard\n", cps);
7718     }
7719 }
7720
7721
7722 void
7723 TwoMachinesEventIfReady P((void))
7724 {
7725   if (first.lastPing != first.lastPong) {
7726     DisplayMessage("", _("Waiting for first chess program"));
7727     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7728     return;
7729   }
7730   if (second.lastPing != second.lastPong) {
7731     DisplayMessage("", _("Waiting for second chess program"));
7732     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7733     return;
7734   }
7735   ThawUI();
7736   TwoMachinesEvent();
7737 }
7738
7739 void
7740 NextMatchGame P((void))
7741 {
7742     int index; /* [HGM] autoinc: step lod index during match */
7743     Reset(FALSE, TRUE);
7744     if (*appData.loadGameFile != NULLCHAR) {
7745         index = appData.loadGameIndex;
7746         if(index < 0) { // [HGM] autoinc
7747             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7748             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7749         } 
7750         LoadGameFromFile(appData.loadGameFile,
7751                          index,
7752                          appData.loadGameFile, FALSE);
7753     } else if (*appData.loadPositionFile != NULLCHAR) {
7754         index = appData.loadPositionIndex;
7755         if(index < 0) { // [HGM] autoinc
7756             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7757             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7758         } 
7759         LoadPositionFromFile(appData.loadPositionFile,
7760                              index,
7761                              appData.loadPositionFile);
7762     }
7763     TwoMachinesEventIfReady();
7764 }
7765
7766 void UserAdjudicationEvent( int result )
7767 {
7768     ChessMove gameResult = GameIsDrawn;
7769
7770     if( result > 0 ) {
7771         gameResult = WhiteWins;
7772     }
7773     else if( result < 0 ) {
7774         gameResult = BlackWins;
7775     }
7776
7777     if( gameMode == TwoMachinesPlay ) {
7778         GameEnds( gameResult, "User adjudication", GE_XBOARD );
7779     }
7780 }
7781
7782
7783 // [HGM] save: calculate checksum of game to make games easily identifiable
7784 int StringCheckSum(char *s)
7785 {
7786         int i = 0;
7787         if(s==NULL) return 0;
7788         while(*s) i = i*259 + *s++;
7789         return i;
7790 }
7791
7792 int GameCheckSum()
7793 {
7794         int i, sum=0;
7795         for(i=backwardMostMove; i<forwardMostMove; i++) {
7796                 sum += pvInfoList[i].depth;
7797                 sum += StringCheckSum(parseList[i]);
7798                 sum += StringCheckSum(commentList[i]);
7799                 sum *= 261;
7800         }
7801         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7802         return sum + StringCheckSum(commentList[i]);
7803 } // end of save patch
7804
7805 void
7806 GameEnds(result, resultDetails, whosays)
7807      ChessMove result;
7808      char *resultDetails;
7809      int whosays;
7810 {
7811     GameMode nextGameMode;
7812     int isIcsGame;
7813     char buf[MSG_SIZ];
7814
7815     if(endingGame) return; /* [HGM] crash: forbid recursion */
7816     endingGame = 1;
7817
7818     if (appData.debugMode) {
7819       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7820               result, resultDetails ? resultDetails : "(null)", whosays);
7821     }
7822
7823     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7824         /* If we are playing on ICS, the server decides when the
7825            game is over, but the engine can offer to draw, claim 
7826            a draw, or resign. 
7827          */
7828 #if ZIPPY
7829         if (appData.zippyPlay && first.initDone) {
7830             if (result == GameIsDrawn) {
7831                 /* In case draw still needs to be claimed */
7832                 SendToICS(ics_prefix);
7833                 SendToICS("draw\n");
7834             } else if (StrCaseStr(resultDetails, "resign")) {
7835                 SendToICS(ics_prefix);
7836                 SendToICS("resign\n");
7837             }
7838         }
7839 #endif
7840         endingGame = 0; /* [HGM] crash */
7841         return;
7842     }
7843
7844     /* If we're loading the game from a file, stop */
7845     if (whosays == GE_FILE) {
7846       (void) StopLoadGameTimer();
7847       gameFileFP = NULL;
7848     }
7849
7850     /* Cancel draw offers */
7851     first.offeredDraw = second.offeredDraw = 0;
7852
7853     /* If this is an ICS game, only ICS can really say it's done;
7854        if not, anyone can. */
7855     isIcsGame = (gameMode == IcsPlayingWhite || 
7856                  gameMode == IcsPlayingBlack || 
7857                  gameMode == IcsObserving    || 
7858                  gameMode == IcsExamining);
7859
7860     if (!isIcsGame || whosays == GE_ICS) {
7861         /* OK -- not an ICS game, or ICS said it was done */
7862         StopClocks();
7863         if (!isIcsGame && !appData.noChessProgram) 
7864           SetUserThinkingEnables();
7865     
7866         /* [HGM] if a machine claims the game end we verify this claim */
7867         if(gameMode == TwoMachinesPlay && appData.testClaims) {
7868             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7869                 char claimer;
7870                 ChessMove trueResult = (ChessMove) -1;
7871
7872                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
7873                                             first.twoMachinesColor[0] :
7874                                             second.twoMachinesColor[0] ;
7875
7876                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7877                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7878                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7879                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7880                 } else
7881                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7882                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7883                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7884                 } else
7885                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7886                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7887                 }
7888
7889                 // now verify win claims, but not in drop games, as we don't understand those yet
7890                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7891                                                  || gameInfo.variant == VariantGreat) &&
7892                     (result == WhiteWins && claimer == 'w' ||
7893                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
7894                       if (appData.debugMode) {
7895                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
7896                                 result, epStatus[forwardMostMove], forwardMostMove);
7897                       }
7898                       if(result != trueResult) {
7899                               sprintf(buf, "False win claim: '%s'", resultDetails);
7900                               result = claimer == 'w' ? BlackWins : WhiteWins;
7901                               resultDetails = buf;
7902                       }
7903                 } else
7904                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7905                     && (forwardMostMove <= backwardMostMove ||
7906                         epStatus[forwardMostMove-1] > EP_DRAWS ||
7907                         (claimer=='b')==(forwardMostMove&1))
7908                                                                                   ) {
7909                       /* [HGM] verify: draws that were not flagged are false claims */
7910                       sprintf(buf, "False draw claim: '%s'", resultDetails);
7911                       result = claimer == 'w' ? BlackWins : WhiteWins;
7912                       resultDetails = buf;
7913                 }
7914                 /* (Claiming a loss is accepted no questions asked!) */
7915             }
7916             /* [HGM] bare: don't allow bare King to win */
7917             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7918                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
7919                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7920                && result != GameIsDrawn)
7921             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7922                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7923                         int p = (int)boards[forwardMostMove][i][j] - color;
7924                         if(p >= 0 && p <= (int)WhiteKing) k++;
7925                 }
7926                 if (appData.debugMode) {
7927                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7928                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7929                 }
7930                 if(k <= 1) {
7931                         result = GameIsDrawn;
7932                         sprintf(buf, "%s but bare king", resultDetails);
7933                         resultDetails = buf;
7934                 }
7935             }
7936         }
7937
7938
7939         if(serverMoves != NULL && !loadFlag) { char c = '=';
7940             if(result==WhiteWins) c = '+';
7941             if(result==BlackWins) c = '-';
7942             if(resultDetails != NULL)
7943                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7944         }
7945         if (resultDetails != NULL) {
7946             gameInfo.result = result;
7947             gameInfo.resultDetails = StrSave(resultDetails);
7948
7949             /* display last move only if game was not loaded from file */
7950             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7951                 DisplayMove(currentMove - 1);
7952     
7953             if (forwardMostMove != 0) {
7954                 if (gameMode != PlayFromGameFile && gameMode != EditGame
7955                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7956                                                                 ) {
7957                     if (*appData.saveGameFile != NULLCHAR) {
7958                         SaveGameToFile(appData.saveGameFile, TRUE);
7959                     } else if (appData.autoSaveGames) {
7960                         AutoSaveGame();
7961                     }
7962                     if (*appData.savePositionFile != NULLCHAR) {
7963                         SavePositionToFile(appData.savePositionFile);
7964                     }
7965                 }
7966             }
7967
7968             /* Tell program how game ended in case it is learning */
7969             /* [HGM] Moved this to after saving the PGN, just in case */
7970             /* engine died and we got here through time loss. In that */
7971             /* case we will get a fatal error writing the pipe, which */
7972             /* would otherwise lose us the PGN.                       */
7973             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
7974             /* output during GameEnds should never be fatal anymore   */
7975             if (gameMode == MachinePlaysWhite ||
7976                 gameMode == MachinePlaysBlack ||
7977                 gameMode == TwoMachinesPlay ||
7978                 gameMode == IcsPlayingWhite ||
7979                 gameMode == IcsPlayingBlack ||
7980                 gameMode == BeginningOfGame) {
7981                 char buf[MSG_SIZ];
7982                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7983                         resultDetails);
7984                 if (first.pr != NoProc) {
7985                     SendToProgram(buf, &first);
7986                 }
7987                 if (second.pr != NoProc &&
7988                     gameMode == TwoMachinesPlay) {
7989                     SendToProgram(buf, &second);
7990                 }
7991             }
7992         }
7993
7994         if (appData.icsActive) {
7995             if (appData.quietPlay &&
7996                 (gameMode == IcsPlayingWhite ||
7997                  gameMode == IcsPlayingBlack)) {
7998                 SendToICS(ics_prefix);
7999                 SendToICS("set shout 1\n");
8000             }
8001             nextGameMode = IcsIdle;
8002             ics_user_moved = FALSE;
8003             /* clean up premove.  It's ugly when the game has ended and the
8004              * premove highlights are still on the board.
8005              */
8006             if (gotPremove) {
8007               gotPremove = FALSE;
8008               ClearPremoveHighlights();
8009               DrawPosition(FALSE, boards[currentMove]);
8010             }
8011             if (whosays == GE_ICS) {
8012                 switch (result) {
8013                 case WhiteWins:
8014                     if (gameMode == IcsPlayingWhite)
8015                         PlayIcsWinSound();
8016                     else if(gameMode == IcsPlayingBlack)
8017                         PlayIcsLossSound();
8018                     break;
8019                 case BlackWins:
8020                     if (gameMode == IcsPlayingBlack)
8021                         PlayIcsWinSound();
8022                     else if(gameMode == IcsPlayingWhite)
8023                         PlayIcsLossSound();
8024                     break;
8025                 case GameIsDrawn:
8026                     PlayIcsDrawSound();
8027                     break;
8028                 default:
8029                     PlayIcsUnfinishedSound();
8030                 }
8031             }
8032         } else if (gameMode == EditGame ||
8033                    gameMode == PlayFromGameFile || 
8034                    gameMode == AnalyzeMode || 
8035                    gameMode == AnalyzeFile) {
8036             nextGameMode = gameMode;
8037         } else {
8038             nextGameMode = EndOfGame;
8039         }
8040         pausing = FALSE;
8041         ModeHighlight();
8042     } else {
8043         nextGameMode = gameMode;
8044     }
8045
8046     if (appData.noChessProgram) {
8047         gameMode = nextGameMode;
8048         ModeHighlight();
8049         endingGame = 0; /* [HGM] crash */
8050         return;
8051     }
8052
8053     if (first.reuse) {
8054         /* Put first chess program into idle state */
8055         if (first.pr != NoProc &&
8056             (gameMode == MachinePlaysWhite ||
8057              gameMode == MachinePlaysBlack ||
8058              gameMode == TwoMachinesPlay ||
8059              gameMode == IcsPlayingWhite ||
8060              gameMode == IcsPlayingBlack ||
8061              gameMode == BeginningOfGame)) {
8062             SendToProgram("force\n", &first);
8063             if (first.usePing) {
8064               char buf[MSG_SIZ];
8065               sprintf(buf, "ping %d\n", ++first.lastPing);
8066               SendToProgram(buf, &first);
8067             }
8068         }
8069     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8070         /* Kill off first chess program */
8071         if (first.isr != NULL)
8072           RemoveInputSource(first.isr);
8073         first.isr = NULL;
8074     
8075         if (first.pr != NoProc) {
8076             ExitAnalyzeMode();
8077             DoSleep( appData.delayBeforeQuit );
8078             SendToProgram("quit\n", &first);
8079             DoSleep( appData.delayAfterQuit );
8080             DestroyChildProcess(first.pr, first.useSigterm);
8081         }
8082         first.pr = NoProc;
8083     }
8084     if (second.reuse) {
8085         /* Put second chess program into idle state */
8086         if (second.pr != NoProc &&
8087             gameMode == TwoMachinesPlay) {
8088             SendToProgram("force\n", &second);
8089             if (second.usePing) {
8090               char buf[MSG_SIZ];
8091               sprintf(buf, "ping %d\n", ++second.lastPing);
8092               SendToProgram(buf, &second);
8093             }
8094         }
8095     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8096         /* Kill off second chess program */
8097         if (second.isr != NULL)
8098           RemoveInputSource(second.isr);
8099         second.isr = NULL;
8100     
8101         if (second.pr != NoProc) {
8102             DoSleep( appData.delayBeforeQuit );
8103             SendToProgram("quit\n", &second);
8104             DoSleep( appData.delayAfterQuit );
8105             DestroyChildProcess(second.pr, second.useSigterm);
8106         }
8107         second.pr = NoProc;
8108     }
8109
8110     if (matchMode && gameMode == TwoMachinesPlay) {
8111         switch (result) {
8112         case WhiteWins:
8113           if (first.twoMachinesColor[0] == 'w') {
8114             first.matchWins++;
8115           } else {
8116             second.matchWins++;
8117           }
8118           break;
8119         case BlackWins:
8120           if (first.twoMachinesColor[0] == 'b') {
8121             first.matchWins++;
8122           } else {
8123             second.matchWins++;
8124           }
8125           break;
8126         default:
8127           break;
8128         }
8129         if (matchGame < appData.matchGames) {
8130             char *tmp;
8131             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8132                 tmp = first.twoMachinesColor;
8133                 first.twoMachinesColor = second.twoMachinesColor;
8134                 second.twoMachinesColor = tmp;
8135             }
8136             gameMode = nextGameMode;
8137             matchGame++;
8138             if(appData.matchPause>10000 || appData.matchPause<10)
8139                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8140             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8141             endingGame = 0; /* [HGM] crash */
8142             return;
8143         } else {
8144             char buf[MSG_SIZ];
8145             gameMode = nextGameMode;
8146             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8147                     first.tidy, second.tidy,
8148                     first.matchWins, second.matchWins,
8149                     appData.matchGames - (first.matchWins + second.matchWins));
8150             DisplayFatalError(buf, 0, 0);
8151         }
8152     }
8153     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8154         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8155       ExitAnalyzeMode();
8156     gameMode = nextGameMode;
8157     ModeHighlight();
8158     endingGame = 0;  /* [HGM] crash */
8159 }
8160
8161 /* Assumes program was just initialized (initString sent).
8162    Leaves program in force mode. */
8163 void
8164 FeedMovesToProgram(cps, upto) 
8165      ChessProgramState *cps;
8166      int upto;
8167 {
8168     int i;
8169     
8170     if (appData.debugMode)
8171       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8172               startedFromSetupPosition ? "position and " : "",
8173               backwardMostMove, upto, cps->which);
8174     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8175         // [HGM] variantswitch: make engine aware of new variant
8176         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8177                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8178         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8179         SendToProgram(buf, cps);
8180         currentlyInitializedVariant = gameInfo.variant;
8181     }
8182     SendToProgram("force\n", cps);
8183     if (startedFromSetupPosition) {
8184         SendBoard(cps, backwardMostMove);
8185     if (appData.debugMode) {
8186         fprintf(debugFP, "feedMoves\n");
8187     }
8188     }
8189     for (i = backwardMostMove; i < upto; i++) {
8190         SendMoveToProgram(i, cps);
8191     }
8192 }
8193
8194
8195 void
8196 ResurrectChessProgram()
8197 {
8198      /* The chess program may have exited.
8199         If so, restart it and feed it all the moves made so far. */
8200
8201     if (appData.noChessProgram || first.pr != NoProc) return;
8202     
8203     StartChessProgram(&first);
8204     InitChessProgram(&first, FALSE);
8205     FeedMovesToProgram(&first, currentMove);
8206
8207     if (!first.sendTime) {
8208         /* can't tell gnuchess what its clock should read,
8209            so we bow to its notion. */
8210         ResetClocks();
8211         timeRemaining[0][currentMove] = whiteTimeRemaining;
8212         timeRemaining[1][currentMove] = blackTimeRemaining;
8213     }
8214
8215     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8216                 appData.icsEngineAnalyze) && first.analysisSupport) {
8217       SendToProgram("analyze\n", &first);
8218       first.analyzing = TRUE;
8219     }
8220 }
8221
8222 /*
8223  * Button procedures
8224  */
8225 void
8226 Reset(redraw, init)
8227      int redraw, init;
8228 {
8229     int i;
8230
8231     if (appData.debugMode) {
8232         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8233                 redraw, init, gameMode);
8234     }
8235     pausing = pauseExamInvalid = FALSE;
8236     startedFromSetupPosition = blackPlaysFirst = FALSE;
8237     firstMove = TRUE;
8238     whiteFlag = blackFlag = FALSE;
8239     userOfferedDraw = FALSE;
8240     hintRequested = bookRequested = FALSE;
8241     first.maybeThinking = FALSE;
8242     second.maybeThinking = FALSE;
8243     first.bookSuspend = FALSE; // [HGM] book
8244     second.bookSuspend = FALSE;
8245     thinkOutput[0] = NULLCHAR;
8246     lastHint[0] = NULLCHAR;
8247     ClearGameInfo(&gameInfo);
8248     gameInfo.variant = StringToVariant(appData.variant);
8249     ics_user_moved = ics_clock_paused = FALSE;
8250     ics_getting_history = H_FALSE;
8251     ics_gamenum = -1;
8252     white_holding[0] = black_holding[0] = NULLCHAR;
8253     ClearProgramStats();
8254     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8255     
8256     ResetFrontEnd();
8257     ClearHighlights();
8258     flipView = appData.flipView;
8259     ClearPremoveHighlights();
8260     gotPremove = FALSE;
8261     alarmSounded = FALSE;
8262
8263     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8264     if(appData.serverMovesName != NULL) {
8265         /* [HGM] prepare to make moves file for broadcasting */
8266         clock_t t = clock();
8267         if(serverMoves != NULL) fclose(serverMoves);
8268         serverMoves = fopen(appData.serverMovesName, "r");
8269         if(serverMoves != NULL) {
8270             fclose(serverMoves);
8271             /* delay 15 sec before overwriting, so all clients can see end */
8272             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8273         }
8274         serverMoves = fopen(appData.serverMovesName, "w");
8275     }
8276
8277     ExitAnalyzeMode();
8278     gameMode = BeginningOfGame;
8279     ModeHighlight();
8280     if(appData.icsActive) gameInfo.variant = VariantNormal;
8281     InitPosition(redraw);
8282     for (i = 0; i < MAX_MOVES; i++) {
8283         if (commentList[i] != NULL) {
8284             free(commentList[i]);
8285             commentList[i] = NULL;
8286         }
8287     }
8288     ResetClocks();
8289     timeRemaining[0][0] = whiteTimeRemaining;
8290     timeRemaining[1][0] = blackTimeRemaining;
8291     if (first.pr == NULL) {
8292         StartChessProgram(&first);
8293     }
8294     if (init) {
8295             InitChessProgram(&first, startedFromSetupPosition);
8296     }
8297     DisplayTitle("");
8298     DisplayMessage("", "");
8299     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8300     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8301 }
8302
8303 void
8304 AutoPlayGameLoop()
8305 {
8306     for (;;) {
8307         if (!AutoPlayOneMove())
8308           return;
8309         if (matchMode || appData.timeDelay == 0)
8310           continue;
8311         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8312           return;
8313         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8314         break;
8315     }
8316 }
8317
8318
8319 int
8320 AutoPlayOneMove()
8321 {
8322     int fromX, fromY, toX, toY;
8323
8324     if (appData.debugMode) {
8325       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8326     }
8327
8328     if (gameMode != PlayFromGameFile)
8329       return FALSE;
8330
8331     if (currentMove >= forwardMostMove) {
8332       gameMode = EditGame;
8333       ModeHighlight();
8334
8335       /* [AS] Clear current move marker at the end of a game */
8336       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8337
8338       return FALSE;
8339     }
8340     
8341     toX = moveList[currentMove][2] - AAA;
8342     toY = moveList[currentMove][3] - ONE;
8343
8344     if (moveList[currentMove][1] == '@') {
8345         if (appData.highlightLastMove) {
8346             SetHighlights(-1, -1, toX, toY);
8347         }
8348     } else {
8349         fromX = moveList[currentMove][0] - AAA;
8350         fromY = moveList[currentMove][1] - ONE;
8351
8352         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8353
8354         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8355
8356         if (appData.highlightLastMove) {
8357             SetHighlights(fromX, fromY, toX, toY);
8358         }
8359     }
8360     DisplayMove(currentMove);
8361     SendMoveToProgram(currentMove++, &first);
8362     DisplayBothClocks();
8363     DrawPosition(FALSE, boards[currentMove]);
8364     // [HGM] PV info: always display, routine tests if empty
8365     DisplayComment(currentMove - 1, commentList[currentMove]);
8366     return TRUE;
8367 }
8368
8369
8370 int
8371 LoadGameOneMove(readAhead)
8372      ChessMove readAhead;
8373 {
8374     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8375     char promoChar = NULLCHAR;
8376     ChessMove moveType;
8377     char move[MSG_SIZ];
8378     char *p, *q;
8379     
8380     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8381         gameMode != AnalyzeMode && gameMode != Training) {
8382         gameFileFP = NULL;
8383         return FALSE;
8384     }
8385     
8386     yyboardindex = forwardMostMove;
8387     if (readAhead != (ChessMove)0) {
8388       moveType = readAhead;
8389     } else {
8390       if (gameFileFP == NULL)
8391           return FALSE;
8392       moveType = (ChessMove) yylex();
8393     }
8394     
8395     done = FALSE;
8396     switch (moveType) {
8397       case Comment:
8398         if (appData.debugMode) 
8399           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8400         p = yy_text;
8401         if (*p == '{' || *p == '[' || *p == '(') {
8402             p[strlen(p) - 1] = NULLCHAR;
8403             p++;
8404         }
8405
8406         /* append the comment but don't display it */
8407         while (*p == '\n') p++;
8408         AppendComment(currentMove, p);
8409         return TRUE;
8410
8411       case WhiteCapturesEnPassant:
8412       case BlackCapturesEnPassant:
8413       case WhitePromotionChancellor:
8414       case BlackPromotionChancellor:
8415       case WhitePromotionArchbishop:
8416       case BlackPromotionArchbishop:
8417       case WhitePromotionCentaur:
8418       case BlackPromotionCentaur:
8419       case WhitePromotionQueen:
8420       case BlackPromotionQueen:
8421       case WhitePromotionRook:
8422       case BlackPromotionRook:
8423       case WhitePromotionBishop:
8424       case BlackPromotionBishop:
8425       case WhitePromotionKnight:
8426       case BlackPromotionKnight:
8427       case WhitePromotionKing:
8428       case BlackPromotionKing:
8429       case NormalMove:
8430       case WhiteKingSideCastle:
8431       case WhiteQueenSideCastle:
8432       case BlackKingSideCastle:
8433       case BlackQueenSideCastle:
8434       case WhiteKingSideCastleWild:
8435       case WhiteQueenSideCastleWild:
8436       case BlackKingSideCastleWild:
8437       case BlackQueenSideCastleWild:
8438       /* PUSH Fabien */
8439       case WhiteHSideCastleFR:
8440       case WhiteASideCastleFR:
8441       case BlackHSideCastleFR:
8442       case BlackASideCastleFR:
8443       /* POP Fabien */
8444         if (appData.debugMode)
8445           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8446         fromX = currentMoveString[0] - AAA;
8447         fromY = currentMoveString[1] - ONE;
8448         toX = currentMoveString[2] - AAA;
8449         toY = currentMoveString[3] - ONE;
8450         promoChar = currentMoveString[4];
8451         break;
8452
8453       case WhiteDrop:
8454       case BlackDrop:
8455         if (appData.debugMode)
8456           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8457         fromX = moveType == WhiteDrop ?
8458           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8459         (int) CharToPiece(ToLower(currentMoveString[0]));
8460         fromY = DROP_RANK;
8461         toX = currentMoveString[2] - AAA;
8462         toY = currentMoveString[3] - ONE;
8463         break;
8464
8465       case WhiteWins:
8466       case BlackWins:
8467       case GameIsDrawn:
8468       case GameUnfinished:
8469         if (appData.debugMode)
8470           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8471         p = strchr(yy_text, '{');
8472         if (p == NULL) p = strchr(yy_text, '(');
8473         if (p == NULL) {
8474             p = yy_text;
8475             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8476         } else {
8477             q = strchr(p, *p == '{' ? '}' : ')');
8478             if (q != NULL) *q = NULLCHAR;
8479             p++;
8480         }
8481         GameEnds(moveType, p, GE_FILE);
8482         done = TRUE;
8483         if (cmailMsgLoaded) {
8484             ClearHighlights();
8485             flipView = WhiteOnMove(currentMove);
8486             if (moveType == GameUnfinished) flipView = !flipView;
8487             if (appData.debugMode)
8488               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8489         }
8490         break;
8491
8492       case (ChessMove) 0:       /* end of file */
8493         if (appData.debugMode)
8494           fprintf(debugFP, "Parser hit end of file\n");
8495         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8496                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8497           case MT_NONE:
8498           case MT_CHECK:
8499             break;
8500           case MT_CHECKMATE:
8501           case MT_STAINMATE:
8502             if (WhiteOnMove(currentMove)) {
8503                 GameEnds(BlackWins, "Black mates", GE_FILE);
8504             } else {
8505                 GameEnds(WhiteWins, "White mates", GE_FILE);
8506             }
8507             break;
8508           case MT_STALEMATE:
8509             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8510             break;
8511         }
8512         done = TRUE;
8513         break;
8514
8515       case MoveNumberOne:
8516         if (lastLoadGameStart == GNUChessGame) {
8517             /* GNUChessGames have numbers, but they aren't move numbers */
8518             if (appData.debugMode)
8519               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8520                       yy_text, (int) moveType);
8521             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8522         }
8523         /* else fall thru */
8524
8525       case XBoardGame:
8526       case GNUChessGame:
8527       case PGNTag:
8528         /* Reached start of next game in file */
8529         if (appData.debugMode)
8530           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8531         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8532                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8533           case MT_NONE:
8534           case MT_CHECK:
8535             break;
8536           case MT_CHECKMATE:
8537           case MT_STAINMATE:
8538             if (WhiteOnMove(currentMove)) {
8539                 GameEnds(BlackWins, "Black mates", GE_FILE);
8540             } else {
8541                 GameEnds(WhiteWins, "White mates", GE_FILE);
8542             }
8543             break;
8544           case MT_STALEMATE:
8545             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8546             break;
8547         }
8548         done = TRUE;
8549         break;
8550
8551       case PositionDiagram:     /* should not happen; ignore */
8552       case ElapsedTime:         /* ignore */
8553       case NAG:                 /* ignore */
8554         if (appData.debugMode)
8555           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8556                   yy_text, (int) moveType);
8557         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8558
8559       case IllegalMove:
8560         if (appData.testLegality) {
8561             if (appData.debugMode)
8562               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8563             sprintf(move, _("Illegal move: %d.%s%s"),
8564                     (forwardMostMove / 2) + 1,
8565                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8566             DisplayError(move, 0);
8567             done = TRUE;
8568         } else {
8569             if (appData.debugMode)
8570               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8571                       yy_text, currentMoveString);
8572             fromX = currentMoveString[0] - AAA;
8573             fromY = currentMoveString[1] - ONE;
8574             toX = currentMoveString[2] - AAA;
8575             toY = currentMoveString[3] - ONE;
8576             promoChar = currentMoveString[4];
8577         }
8578         break;
8579
8580       case AmbiguousMove:
8581         if (appData.debugMode)
8582           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8583         sprintf(move, _("Ambiguous move: %d.%s%s"),
8584                 (forwardMostMove / 2) + 1,
8585                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8586         DisplayError(move, 0);
8587         done = TRUE;
8588         break;
8589
8590       default:
8591       case ImpossibleMove:
8592         if (appData.debugMode)
8593           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8594         sprintf(move, _("Illegal move: %d.%s%s"),
8595                 (forwardMostMove / 2) + 1,
8596                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8597         DisplayError(move, 0);
8598         done = TRUE;
8599         break;
8600     }
8601
8602     if (done) {
8603         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8604             DrawPosition(FALSE, boards[currentMove]);
8605             DisplayBothClocks();
8606             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8607               DisplayComment(currentMove - 1, commentList[currentMove]);
8608         }
8609         (void) StopLoadGameTimer();
8610         gameFileFP = NULL;
8611         cmailOldMove = forwardMostMove;
8612         return FALSE;
8613     } else {
8614         /* currentMoveString is set as a side-effect of yylex */
8615         strcat(currentMoveString, "\n");
8616         strcpy(moveList[forwardMostMove], currentMoveString);
8617         
8618         thinkOutput[0] = NULLCHAR;
8619         MakeMove(fromX, fromY, toX, toY, promoChar);
8620         currentMove = forwardMostMove;
8621         return TRUE;
8622     }
8623 }
8624
8625 /* Load the nth game from the given file */
8626 int
8627 LoadGameFromFile(filename, n, title, useList)
8628      char *filename;
8629      int n;
8630      char *title;
8631      /*Boolean*/ int useList;
8632 {
8633     FILE *f;
8634     char buf[MSG_SIZ];
8635
8636     if (strcmp(filename, "-") == 0) {
8637         f = stdin;
8638         title = "stdin";
8639     } else {
8640         f = fopen(filename, "rb");
8641         if (f == NULL) {
8642           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8643             DisplayError(buf, errno);
8644             return FALSE;
8645         }
8646     }
8647     if (fseek(f, 0, 0) == -1) {
8648         /* f is not seekable; probably a pipe */
8649         useList = FALSE;
8650     }
8651     if (useList && n == 0) {
8652         int error = GameListBuild(f);
8653         if (error) {
8654             DisplayError(_("Cannot build game list"), error);
8655         } else if (!ListEmpty(&gameList) &&
8656                    ((ListGame *) gameList.tailPred)->number > 1) {
8657             GameListPopUp(f, title);
8658             return TRUE;
8659         }
8660         GameListDestroy();
8661         n = 1;
8662     }
8663     if (n == 0) n = 1;
8664     return LoadGame(f, n, title, FALSE);
8665 }
8666
8667
8668 void
8669 MakeRegisteredMove()
8670 {
8671     int fromX, fromY, toX, toY;
8672     char promoChar;
8673     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8674         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8675           case CMAIL_MOVE:
8676           case CMAIL_DRAW:
8677             if (appData.debugMode)
8678               fprintf(debugFP, "Restoring %s for game %d\n",
8679                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8680     
8681             thinkOutput[0] = NULLCHAR;
8682             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8683             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8684             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8685             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8686             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8687             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8688             MakeMove(fromX, fromY, toX, toY, promoChar);
8689             ShowMove(fromX, fromY, toX, toY);
8690               
8691             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8692                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8693               case MT_NONE:
8694               case MT_CHECK:
8695                 break;
8696                 
8697               case MT_CHECKMATE:
8698               case MT_STAINMATE:
8699                 if (WhiteOnMove(currentMove)) {
8700                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8701                 } else {
8702                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8703                 }
8704                 break;
8705                 
8706               case MT_STALEMATE:
8707                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8708                 break;
8709             }
8710
8711             break;
8712             
8713           case CMAIL_RESIGN:
8714             if (WhiteOnMove(currentMove)) {
8715                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8716             } else {
8717                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8718             }
8719             break;
8720             
8721           case CMAIL_ACCEPT:
8722             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8723             break;
8724               
8725           default:
8726             break;
8727         }
8728     }
8729
8730     return;
8731 }
8732
8733 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8734 int
8735 CmailLoadGame(f, gameNumber, title, useList)
8736      FILE *f;
8737      int gameNumber;
8738      char *title;
8739      int useList;
8740 {
8741     int retVal;
8742
8743     if (gameNumber > nCmailGames) {
8744         DisplayError(_("No more games in this message"), 0);
8745         return FALSE;
8746     }
8747     if (f == lastLoadGameFP) {
8748         int offset = gameNumber - lastLoadGameNumber;
8749         if (offset == 0) {
8750             cmailMsg[0] = NULLCHAR;
8751             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8752                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8753                 nCmailMovesRegistered--;
8754             }
8755             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8756             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8757                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8758             }
8759         } else {
8760             if (! RegisterMove()) return FALSE;
8761         }
8762     }
8763
8764     retVal = LoadGame(f, gameNumber, title, useList);
8765
8766     /* Make move registered during previous look at this game, if any */
8767     MakeRegisteredMove();
8768
8769     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8770         commentList[currentMove]
8771           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8772         DisplayComment(currentMove - 1, commentList[currentMove]);
8773     }
8774
8775     return retVal;
8776 }
8777
8778 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8779 int
8780 ReloadGame(offset)
8781      int offset;
8782 {
8783     int gameNumber = lastLoadGameNumber + offset;
8784     if (lastLoadGameFP == NULL) {
8785         DisplayError(_("No game has been loaded yet"), 0);
8786         return FALSE;
8787     }
8788     if (gameNumber <= 0) {
8789         DisplayError(_("Can't back up any further"), 0);
8790         return FALSE;
8791     }
8792     if (cmailMsgLoaded) {
8793         return CmailLoadGame(lastLoadGameFP, gameNumber,
8794                              lastLoadGameTitle, lastLoadGameUseList);
8795     } else {
8796         return LoadGame(lastLoadGameFP, gameNumber,
8797                         lastLoadGameTitle, lastLoadGameUseList);
8798     }
8799 }
8800
8801
8802
8803 /* Load the nth game from open file f */
8804 int
8805 LoadGame(f, gameNumber, title, useList)
8806      FILE *f;
8807      int gameNumber;
8808      char *title;
8809      int useList;
8810 {
8811     ChessMove cm;
8812     char buf[MSG_SIZ];
8813     int gn = gameNumber;
8814     ListGame *lg = NULL;
8815     int numPGNTags = 0;
8816     int err;
8817     GameMode oldGameMode;
8818     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8819
8820     if (appData.debugMode) 
8821         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8822
8823     if (gameMode == Training )
8824         SetTrainingModeOff();
8825
8826     oldGameMode = gameMode;
8827     if (gameMode != BeginningOfGame) {
8828       Reset(FALSE, TRUE);
8829     }
8830
8831     gameFileFP = f;
8832     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8833         fclose(lastLoadGameFP);
8834     }
8835
8836     if (useList) {
8837         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8838         
8839         if (lg) {
8840             fseek(f, lg->offset, 0);
8841             GameListHighlight(gameNumber);
8842             gn = 1;
8843         }
8844         else {
8845             DisplayError(_("Game number out of range"), 0);
8846             return FALSE;
8847         }
8848     } else {
8849         GameListDestroy();
8850         if (fseek(f, 0, 0) == -1) {
8851             if (f == lastLoadGameFP ?
8852                 gameNumber == lastLoadGameNumber + 1 :
8853                 gameNumber == 1) {
8854                 gn = 1;
8855             } else {
8856                 DisplayError(_("Can't seek on game file"), 0);
8857                 return FALSE;
8858             }
8859         }
8860     }
8861     lastLoadGameFP = f;
8862     lastLoadGameNumber = gameNumber;
8863     strcpy(lastLoadGameTitle, title);
8864     lastLoadGameUseList = useList;
8865
8866     yynewfile(f);
8867
8868     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8869       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8870                 lg->gameInfo.black);
8871             DisplayTitle(buf);
8872     } else if (*title != NULLCHAR) {
8873         if (gameNumber > 1) {
8874             sprintf(buf, "%s %d", title, gameNumber);
8875             DisplayTitle(buf);
8876         } else {
8877             DisplayTitle(title);
8878         }
8879     }
8880
8881     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8882         gameMode = PlayFromGameFile;
8883         ModeHighlight();
8884     }
8885
8886     currentMove = forwardMostMove = backwardMostMove = 0;
8887     CopyBoard(boards[0], initialPosition);
8888     StopClocks();
8889
8890     /*
8891      * Skip the first gn-1 games in the file.
8892      * Also skip over anything that precedes an identifiable 
8893      * start of game marker, to avoid being confused by 
8894      * garbage at the start of the file.  Currently 
8895      * recognized start of game markers are the move number "1",
8896      * the pattern "gnuchess .* game", the pattern
8897      * "^[#;%] [^ ]* game file", and a PGN tag block.  
8898      * A game that starts with one of the latter two patterns
8899      * will also have a move number 1, possibly
8900      * following a position diagram.
8901      * 5-4-02: Let's try being more lenient and allowing a game to
8902      * start with an unnumbered move.  Does that break anything?
8903      */
8904     cm = lastLoadGameStart = (ChessMove) 0;
8905     while (gn > 0) {
8906         yyboardindex = forwardMostMove;
8907         cm = (ChessMove) yylex();
8908         switch (cm) {
8909           case (ChessMove) 0:
8910             if (cmailMsgLoaded) {
8911                 nCmailGames = CMAIL_MAX_GAMES - gn;
8912             } else {
8913                 Reset(TRUE, TRUE);
8914                 DisplayError(_("Game not found in file"), 0);
8915             }
8916             return FALSE;
8917
8918           case GNUChessGame:
8919           case XBoardGame:
8920             gn--;
8921             lastLoadGameStart = cm;
8922             break;
8923             
8924           case MoveNumberOne:
8925             switch (lastLoadGameStart) {
8926               case GNUChessGame:
8927               case XBoardGame:
8928               case PGNTag:
8929                 break;
8930               case MoveNumberOne:
8931               case (ChessMove) 0:
8932                 gn--;           /* count this game */
8933                 lastLoadGameStart = cm;
8934                 break;
8935               default:
8936                 /* impossible */
8937                 break;
8938             }
8939             break;
8940
8941           case PGNTag:
8942             switch (lastLoadGameStart) {
8943               case GNUChessGame:
8944               case PGNTag:
8945               case MoveNumberOne:
8946               case (ChessMove) 0:
8947                 gn--;           /* count this game */
8948                 lastLoadGameStart = cm;
8949                 break;
8950               case XBoardGame:
8951                 lastLoadGameStart = cm; /* game counted already */
8952                 break;
8953               default:
8954                 /* impossible */
8955                 break;
8956             }
8957             if (gn > 0) {
8958                 do {
8959                     yyboardindex = forwardMostMove;
8960                     cm = (ChessMove) yylex();
8961                 } while (cm == PGNTag || cm == Comment);
8962             }
8963             break;
8964
8965           case WhiteWins:
8966           case BlackWins:
8967           case GameIsDrawn:
8968             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8969                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
8970                     != CMAIL_OLD_RESULT) {
8971                     nCmailResults ++ ;
8972                     cmailResult[  CMAIL_MAX_GAMES
8973                                 - gn - 1] = CMAIL_OLD_RESULT;
8974                 }
8975             }
8976             break;
8977
8978           case NormalMove:
8979             /* Only a NormalMove can be at the start of a game
8980              * without a position diagram. */
8981             if (lastLoadGameStart == (ChessMove) 0) {
8982               gn--;
8983               lastLoadGameStart = MoveNumberOne;
8984             }
8985             break;
8986
8987           default:
8988             break;
8989         }
8990     }
8991     
8992     if (appData.debugMode)
8993       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8994
8995     if (cm == XBoardGame) {
8996         /* Skip any header junk before position diagram and/or move 1 */
8997         for (;;) {
8998             yyboardindex = forwardMostMove;
8999             cm = (ChessMove) yylex();
9000
9001             if (cm == (ChessMove) 0 ||
9002                 cm == GNUChessGame || cm == XBoardGame) {
9003                 /* Empty game; pretend end-of-file and handle later */
9004                 cm = (ChessMove) 0;
9005                 break;
9006             }
9007
9008             if (cm == MoveNumberOne || cm == PositionDiagram ||
9009                 cm == PGNTag || cm == Comment)
9010               break;
9011         }
9012     } else if (cm == GNUChessGame) {
9013         if (gameInfo.event != NULL) {
9014             free(gameInfo.event);
9015         }
9016         gameInfo.event = StrSave(yy_text);
9017     }   
9018
9019     startedFromSetupPosition = FALSE;
9020     while (cm == PGNTag) {
9021         if (appData.debugMode) 
9022           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9023         err = ParsePGNTag(yy_text, &gameInfo);
9024         if (!err) numPGNTags++;
9025
9026         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9027         if(gameInfo.variant != oldVariant) {
9028             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9029             InitPosition(TRUE);
9030             oldVariant = gameInfo.variant;
9031             if (appData.debugMode) 
9032               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9033         }
9034
9035
9036         if (gameInfo.fen != NULL) {
9037           Board initial_position;
9038           startedFromSetupPosition = TRUE;
9039           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9040             Reset(TRUE, TRUE);
9041             DisplayError(_("Bad FEN position in file"), 0);
9042             return FALSE;
9043           }
9044           CopyBoard(boards[0], initial_position);
9045           if (blackPlaysFirst) {
9046             currentMove = forwardMostMove = backwardMostMove = 1;
9047             CopyBoard(boards[1], initial_position);
9048             strcpy(moveList[0], "");
9049             strcpy(parseList[0], "");
9050             timeRemaining[0][1] = whiteTimeRemaining;
9051             timeRemaining[1][1] = blackTimeRemaining;
9052             if (commentList[0] != NULL) {
9053               commentList[1] = commentList[0];
9054               commentList[0] = NULL;
9055             }
9056           } else {
9057             currentMove = forwardMostMove = backwardMostMove = 0;
9058           }
9059           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9060           {   int i;
9061               initialRulePlies = FENrulePlies;
9062               epStatus[forwardMostMove] = FENepStatus;
9063               for( i=0; i< nrCastlingRights; i++ )
9064                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9065           }
9066           yyboardindex = forwardMostMove;
9067           free(gameInfo.fen);
9068           gameInfo.fen = NULL;
9069         }
9070
9071         yyboardindex = forwardMostMove;
9072         cm = (ChessMove) yylex();
9073
9074         /* Handle comments interspersed among the tags */
9075         while (cm == Comment) {
9076             char *p;
9077             if (appData.debugMode) 
9078               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9079             p = yy_text;
9080             if (*p == '{' || *p == '[' || *p == '(') {
9081                 p[strlen(p) - 1] = NULLCHAR;
9082                 p++;
9083             }
9084             while (*p == '\n') p++;
9085             AppendComment(currentMove, p);
9086             yyboardindex = forwardMostMove;
9087             cm = (ChessMove) yylex();
9088         }
9089     }
9090
9091     /* don't rely on existence of Event tag since if game was
9092      * pasted from clipboard the Event tag may not exist
9093      */
9094     if (numPGNTags > 0){
9095         char *tags;
9096         if (gameInfo.variant == VariantNormal) {
9097           gameInfo.variant = StringToVariant(gameInfo.event);
9098         }
9099         if (!matchMode) {
9100           if( appData.autoDisplayTags ) {
9101             tags = PGNTags(&gameInfo);
9102             TagsPopUp(tags, CmailMsg());
9103             free(tags);
9104           }
9105         }
9106     } else {
9107         /* Make something up, but don't display it now */
9108         SetGameInfo();
9109         TagsPopDown();
9110     }
9111
9112     if (cm == PositionDiagram) {
9113         int i, j;
9114         char *p;
9115         Board initial_position;
9116
9117         if (appData.debugMode)
9118           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9119
9120         if (!startedFromSetupPosition) {
9121             p = yy_text;
9122             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9123               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9124                 switch (*p) {
9125                   case '[':
9126                   case '-':
9127                   case ' ':
9128                   case '\t':
9129                   case '\n':
9130                   case '\r':
9131                     break;
9132                   default:
9133                     initial_position[i][j++] = CharToPiece(*p);
9134                     break;
9135                 }
9136             while (*p == ' ' || *p == '\t' ||
9137                    *p == '\n' || *p == '\r') p++;
9138         
9139             if (strncmp(p, "black", strlen("black"))==0)
9140               blackPlaysFirst = TRUE;
9141             else
9142               blackPlaysFirst = FALSE;
9143             startedFromSetupPosition = TRUE;
9144         
9145             CopyBoard(boards[0], initial_position);
9146             if (blackPlaysFirst) {
9147                 currentMove = forwardMostMove = backwardMostMove = 1;
9148                 CopyBoard(boards[1], initial_position);
9149                 strcpy(moveList[0], "");
9150                 strcpy(parseList[0], "");
9151                 timeRemaining[0][1] = whiteTimeRemaining;
9152                 timeRemaining[1][1] = blackTimeRemaining;
9153                 if (commentList[0] != NULL) {
9154                     commentList[1] = commentList[0];
9155                     commentList[0] = NULL;
9156                 }
9157             } else {
9158                 currentMove = forwardMostMove = backwardMostMove = 0;
9159             }
9160         }
9161         yyboardindex = forwardMostMove;
9162         cm = (ChessMove) yylex();
9163     }
9164
9165     if (first.pr == NoProc) {
9166         StartChessProgram(&first);
9167     }
9168     InitChessProgram(&first, FALSE);
9169     SendToProgram("force\n", &first);
9170     if (startedFromSetupPosition) {
9171         SendBoard(&first, forwardMostMove);
9172     if (appData.debugMode) {
9173         fprintf(debugFP, "Load Game\n");
9174     }
9175         DisplayBothClocks();
9176     }      
9177
9178     /* [HGM] server: flag to write setup moves in broadcast file as one */
9179     loadFlag = appData.suppressLoadMoves;
9180
9181     while (cm == Comment) {
9182         char *p;
9183         if (appData.debugMode) 
9184           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9185         p = yy_text;
9186         if (*p == '{' || *p == '[' || *p == '(') {
9187             p[strlen(p) - 1] = NULLCHAR;
9188             p++;
9189         }
9190         while (*p == '\n') p++;
9191         AppendComment(currentMove, p);
9192         yyboardindex = forwardMostMove;
9193         cm = (ChessMove) yylex();
9194     }
9195
9196     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9197         cm == WhiteWins || cm == BlackWins ||
9198         cm == GameIsDrawn || cm == GameUnfinished) {
9199         DisplayMessage("", _("No moves in game"));
9200         if (cmailMsgLoaded) {
9201             if (appData.debugMode)
9202               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9203             ClearHighlights();
9204             flipView = FALSE;
9205         }
9206         DrawPosition(FALSE, boards[currentMove]);
9207         DisplayBothClocks();
9208         gameMode = EditGame;
9209         ModeHighlight();
9210         gameFileFP = NULL;
9211         cmailOldMove = 0;
9212         return TRUE;
9213     }
9214
9215     // [HGM] PV info: routine tests if comment empty
9216     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9217         DisplayComment(currentMove - 1, commentList[currentMove]);
9218     }
9219     if (!matchMode && appData.timeDelay != 0) 
9220       DrawPosition(FALSE, boards[currentMove]);
9221
9222     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9223       programStats.ok_to_send = 1;
9224     }
9225
9226     /* if the first token after the PGN tags is a move
9227      * and not move number 1, retrieve it from the parser 
9228      */
9229     if (cm != MoveNumberOne)
9230         LoadGameOneMove(cm);
9231
9232     /* load the remaining moves from the file */
9233     while (LoadGameOneMove((ChessMove)0)) {
9234       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9235       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9236     }
9237
9238     /* rewind to the start of the game */
9239     currentMove = backwardMostMove;
9240
9241     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9242
9243     if (oldGameMode == AnalyzeFile ||
9244         oldGameMode == AnalyzeMode) {
9245       AnalyzeFileEvent();
9246     }
9247
9248     if (matchMode || appData.timeDelay == 0) {
9249       ToEndEvent();
9250       gameMode = EditGame;
9251       ModeHighlight();
9252     } else if (appData.timeDelay > 0) {
9253       AutoPlayGameLoop();
9254     }
9255
9256     if (appData.debugMode) 
9257         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9258
9259     loadFlag = 0; /* [HGM] true game starts */
9260     return TRUE;
9261 }
9262
9263 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9264 int
9265 ReloadPosition(offset)
9266      int offset;
9267 {
9268     int positionNumber = lastLoadPositionNumber + offset;
9269     if (lastLoadPositionFP == NULL) {
9270         DisplayError(_("No position has been loaded yet"), 0);
9271         return FALSE;
9272     }
9273     if (positionNumber <= 0) {
9274         DisplayError(_("Can't back up any further"), 0);
9275         return FALSE;
9276     }
9277     return LoadPosition(lastLoadPositionFP, positionNumber,
9278                         lastLoadPositionTitle);
9279 }
9280
9281 /* Load the nth position from the given file */
9282 int
9283 LoadPositionFromFile(filename, n, title)
9284      char *filename;
9285      int n;
9286      char *title;
9287 {
9288     FILE *f;
9289     char buf[MSG_SIZ];
9290
9291     if (strcmp(filename, "-") == 0) {
9292         return LoadPosition(stdin, n, "stdin");
9293     } else {
9294         f = fopen(filename, "rb");
9295         if (f == NULL) {
9296             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9297             DisplayError(buf, errno);
9298             return FALSE;
9299         } else {
9300             return LoadPosition(f, n, title);
9301         }
9302     }
9303 }
9304
9305 /* Load the nth position from the given open file, and close it */
9306 int
9307 LoadPosition(f, positionNumber, title)
9308      FILE *f;
9309      int positionNumber;
9310      char *title;
9311 {
9312     char *p, line[MSG_SIZ];
9313     Board initial_position;
9314     int i, j, fenMode, pn;
9315     
9316     if (gameMode == Training )
9317         SetTrainingModeOff();
9318
9319     if (gameMode != BeginningOfGame) {
9320         Reset(FALSE, TRUE);
9321     }
9322     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9323         fclose(lastLoadPositionFP);
9324     }
9325     if (positionNumber == 0) positionNumber = 1;
9326     lastLoadPositionFP = f;
9327     lastLoadPositionNumber = positionNumber;
9328     strcpy(lastLoadPositionTitle, title);
9329     if (first.pr == NoProc) {
9330       StartChessProgram(&first);
9331       InitChessProgram(&first, FALSE);
9332     }    
9333     pn = positionNumber;
9334     if (positionNumber < 0) {
9335         /* Negative position number means to seek to that byte offset */
9336         if (fseek(f, -positionNumber, 0) == -1) {
9337             DisplayError(_("Can't seek on position file"), 0);
9338             return FALSE;
9339         };
9340         pn = 1;
9341     } else {
9342         if (fseek(f, 0, 0) == -1) {
9343             if (f == lastLoadPositionFP ?
9344                 positionNumber == lastLoadPositionNumber + 1 :
9345                 positionNumber == 1) {
9346                 pn = 1;
9347             } else {
9348                 DisplayError(_("Can't seek on position file"), 0);
9349                 return FALSE;
9350             }
9351         }
9352     }
9353     /* See if this file is FEN or old-style xboard */
9354     if (fgets(line, MSG_SIZ, f) == NULL) {
9355         DisplayError(_("Position not found in file"), 0);
9356         return FALSE;
9357     }
9358 #if 0
9359     switch (line[0]) {
9360       case '#':  case 'x':
9361       default:
9362         fenMode = FALSE;
9363         break;
9364       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':
9365       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':
9366       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':
9367       case '7':  case '8':  case '9':
9368       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':
9369       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':
9370       case 'C':  case 'W':             case 'c':  case 'w': 
9371         fenMode = TRUE;
9372         break;
9373     }
9374 #else
9375     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9376     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9377 #endif
9378
9379     if (pn >= 2) {
9380         if (fenMode || line[0] == '#') pn--;
9381         while (pn > 0) {
9382             /* skip positions before number pn */
9383             if (fgets(line, MSG_SIZ, f) == NULL) {
9384                 Reset(TRUE, TRUE);
9385                 DisplayError(_("Position not found in file"), 0);
9386                 return FALSE;
9387             }
9388             if (fenMode || line[0] == '#') pn--;
9389         }
9390     }
9391
9392     if (fenMode) {
9393         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9394             DisplayError(_("Bad FEN position in file"), 0);
9395             return FALSE;
9396         }
9397     } else {
9398         (void) fgets(line, MSG_SIZ, f);
9399         (void) fgets(line, MSG_SIZ, f);
9400     
9401         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9402             (void) fgets(line, MSG_SIZ, f);
9403             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9404                 if (*p == ' ')
9405                   continue;
9406                 initial_position[i][j++] = CharToPiece(*p);
9407             }
9408         }
9409     
9410         blackPlaysFirst = FALSE;
9411         if (!feof(f)) {
9412             (void) fgets(line, MSG_SIZ, f);
9413             if (strncmp(line, "black", strlen("black"))==0)
9414               blackPlaysFirst = TRUE;
9415         }
9416     }
9417     startedFromSetupPosition = TRUE;
9418     
9419     SendToProgram("force\n", &first);
9420     CopyBoard(boards[0], initial_position);
9421     if (blackPlaysFirst) {
9422         currentMove = forwardMostMove = backwardMostMove = 1;
9423         strcpy(moveList[0], "");
9424         strcpy(parseList[0], "");
9425         CopyBoard(boards[1], initial_position);
9426         DisplayMessage("", _("Black to play"));
9427     } else {
9428         currentMove = forwardMostMove = backwardMostMove = 0;
9429         DisplayMessage("", _("White to play"));
9430     }
9431           /* [HGM] copy FEN attributes as well */
9432           {   int i;
9433               initialRulePlies = FENrulePlies;
9434               epStatus[forwardMostMove] = FENepStatus;
9435               for( i=0; i< nrCastlingRights; i++ )
9436                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9437           }
9438     SendBoard(&first, forwardMostMove);
9439     if (appData.debugMode) {
9440 int i, j;
9441   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9442   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9443         fprintf(debugFP, "Load Position\n");
9444     }
9445
9446     if (positionNumber > 1) {
9447         sprintf(line, "%s %d", title, positionNumber);
9448         DisplayTitle(line);
9449     } else {
9450         DisplayTitle(title);
9451     }
9452     gameMode = EditGame;
9453     ModeHighlight();
9454     ResetClocks();
9455     timeRemaining[0][1] = whiteTimeRemaining;
9456     timeRemaining[1][1] = blackTimeRemaining;
9457     DrawPosition(FALSE, boards[currentMove]);
9458    
9459     return TRUE;
9460 }
9461
9462
9463 void
9464 CopyPlayerNameIntoFileName(dest, src)
9465      char **dest, *src;
9466 {
9467     while (*src != NULLCHAR && *src != ',') {
9468         if (*src == ' ') {
9469             *(*dest)++ = '_';
9470             src++;
9471         } else {
9472             *(*dest)++ = *src++;
9473         }
9474     }
9475 }
9476
9477 char *DefaultFileName(ext)
9478      char *ext;
9479 {
9480     static char def[MSG_SIZ];
9481     char *p;
9482
9483     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9484         p = def;
9485         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9486         *p++ = '-';
9487         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9488         *p++ = '.';
9489         strcpy(p, ext);
9490     } else {
9491         def[0] = NULLCHAR;
9492     }
9493     return def;
9494 }
9495
9496 /* Save the current game to the given file */
9497 int
9498 SaveGameToFile(filename, append)
9499      char *filename;
9500      int append;
9501 {
9502     FILE *f;
9503     char buf[MSG_SIZ];
9504
9505     if (strcmp(filename, "-") == 0) {
9506         return SaveGame(stdout, 0, NULL);
9507     } else {
9508         f = fopen(filename, append ? "a" : "w");
9509         if (f == NULL) {
9510             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9511             DisplayError(buf, errno);
9512             return FALSE;
9513         } else {
9514             return SaveGame(f, 0, NULL);
9515         }
9516     }
9517 }
9518
9519 char *
9520 SavePart(str)
9521      char *str;
9522 {
9523     static char buf[MSG_SIZ];
9524     char *p;
9525     
9526     p = strchr(str, ' ');
9527     if (p == NULL) return str;
9528     strncpy(buf, str, p - str);
9529     buf[p - str] = NULLCHAR;
9530     return buf;
9531 }
9532
9533 #define PGN_MAX_LINE 75
9534
9535 #define PGN_SIDE_WHITE  0
9536 #define PGN_SIDE_BLACK  1
9537
9538 /* [AS] */
9539 static int FindFirstMoveOutOfBook( int side )
9540 {
9541     int result = -1;
9542
9543     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9544         int index = backwardMostMove;
9545         int has_book_hit = 0;
9546
9547         if( (index % 2) != side ) {
9548             index++;
9549         }
9550
9551         while( index < forwardMostMove ) {
9552             /* Check to see if engine is in book */
9553             int depth = pvInfoList[index].depth;
9554             int score = pvInfoList[index].score;
9555             int in_book = 0;
9556
9557             if( depth <= 2 ) {
9558                 in_book = 1;
9559             }
9560             else if( score == 0 && depth == 63 ) {
9561                 in_book = 1; /* Zappa */
9562             }
9563             else if( score == 2 && depth == 99 ) {
9564                 in_book = 1; /* Abrok */
9565             }
9566
9567             has_book_hit += in_book;
9568
9569             if( ! in_book ) {
9570                 result = index;
9571
9572                 break;
9573             }
9574
9575             index += 2;
9576         }
9577     }
9578
9579     return result;
9580 }
9581
9582 /* [AS] */
9583 void GetOutOfBookInfo( char * buf )
9584 {
9585     int oob[2];
9586     int i;
9587     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9588
9589     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9590     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9591
9592     *buf = '\0';
9593
9594     if( oob[0] >= 0 || oob[1] >= 0 ) {
9595         for( i=0; i<2; i++ ) {
9596             int idx = oob[i];
9597
9598             if( idx >= 0 ) {
9599                 if( i > 0 && oob[0] >= 0 ) {
9600                     strcat( buf, "   " );
9601                 }
9602
9603                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9604                 sprintf( buf+strlen(buf), "%s%.2f", 
9605                     pvInfoList[idx].score >= 0 ? "+" : "",
9606                     pvInfoList[idx].score / 100.0 );
9607             }
9608         }
9609     }
9610 }
9611
9612 /* Save game in PGN style and close the file */
9613 int
9614 SaveGamePGN(f)
9615      FILE *f;
9616 {
9617     int i, offset, linelen, newblock;
9618     time_t tm;
9619 //    char *movetext;
9620     char numtext[32];
9621     int movelen, numlen, blank;
9622     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9623
9624     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9625     
9626     tm = time((time_t *) NULL);
9627     
9628     PrintPGNTags(f, &gameInfo);
9629     
9630     if (backwardMostMove > 0 || startedFromSetupPosition) {
9631         char *fen = PositionToFEN(backwardMostMove, NULL);
9632         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9633         fprintf(f, "\n{--------------\n");
9634         PrintPosition(f, backwardMostMove);
9635         fprintf(f, "--------------}\n");
9636         free(fen);
9637     }
9638     else {
9639         /* [AS] Out of book annotation */
9640         if( appData.saveOutOfBookInfo ) {
9641             char buf[64];
9642
9643             GetOutOfBookInfo( buf );
9644
9645             if( buf[0] != '\0' ) {
9646                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9647             }
9648         }
9649
9650         fprintf(f, "\n");
9651     }
9652
9653     i = backwardMostMove;
9654     linelen = 0;
9655     newblock = TRUE;
9656
9657     while (i < forwardMostMove) {
9658         /* Print comments preceding this move */
9659         if (commentList[i] != NULL) {
9660             if (linelen > 0) fprintf(f, "\n");
9661             fprintf(f, "{\n%s}\n", commentList[i]);
9662             linelen = 0;
9663             newblock = TRUE;
9664         }
9665
9666         /* Format move number */
9667         if ((i % 2) == 0) {
9668             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9669         } else {
9670             if (newblock) {
9671                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9672             } else {
9673                 numtext[0] = NULLCHAR;
9674             }
9675         }
9676         numlen = strlen(numtext);
9677         newblock = FALSE;
9678
9679         /* Print move number */
9680         blank = linelen > 0 && numlen > 0;
9681         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9682             fprintf(f, "\n");
9683             linelen = 0;
9684             blank = 0;
9685         }
9686         if (blank) {
9687             fprintf(f, " ");
9688             linelen++;
9689         }
9690         fprintf(f, numtext);
9691         linelen += numlen;
9692
9693         /* Get move */
9694         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9695         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9696 #if 0
9697         // SavePart already does this!
9698         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9699                 int p = movelen - 1;
9700                 if(move_buffer[p] == ' ') p--;
9701                 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
9702                     while(p && move_buffer[--p] != '(');
9703                     if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
9704                 }
9705         }
9706 #endif
9707         /* Print move */
9708         blank = linelen > 0 && movelen > 0;
9709         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9710             fprintf(f, "\n");
9711             linelen = 0;
9712             blank = 0;
9713         }
9714         if (blank) {
9715             fprintf(f, " ");
9716             linelen++;
9717         }
9718         fprintf(f, move_buffer);
9719         linelen += movelen;
9720
9721         /* [AS] Add PV info if present */
9722         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9723             /* [HGM] add time */
9724             char buf[MSG_SIZ]; int seconds = 0;
9725
9726 #if 1
9727             if(i >= backwardMostMove) {
9728                 if(WhiteOnMove(i))
9729                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9730                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9731                 else
9732                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9733                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9734             }
9735             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9736 #else
9737             seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
9738 #endif
9739
9740             if( seconds <= 0) buf[0] = 0; else
9741             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9742                 seconds = (seconds + 4)/10; // round to full seconds
9743                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9744                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9745             }
9746
9747             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9748                 pvInfoList[i].score >= 0 ? "+" : "",
9749                 pvInfoList[i].score / 100.0,
9750                 pvInfoList[i].depth,
9751                 buf );
9752
9753             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9754
9755             /* Print score/depth */
9756             blank = linelen > 0 && movelen > 0;
9757             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9758                 fprintf(f, "\n");
9759                 linelen = 0;
9760                 blank = 0;
9761             }
9762             if (blank) {
9763                 fprintf(f, " ");
9764                 linelen++;
9765             }
9766             fprintf(f, move_buffer);
9767             linelen += movelen;
9768         }
9769
9770         i++;
9771     }
9772     
9773     /* Start a new line */
9774     if (linelen > 0) fprintf(f, "\n");
9775
9776     /* Print comments after last move */
9777     if (commentList[i] != NULL) {
9778         fprintf(f, "{\n%s}\n", commentList[i]);
9779     }
9780
9781     /* Print result */
9782     if (gameInfo.resultDetails != NULL &&
9783         gameInfo.resultDetails[0] != NULLCHAR) {
9784         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9785                 PGNResult(gameInfo.result));
9786     } else {
9787         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9788     }
9789
9790     fclose(f);
9791     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9792     return TRUE;
9793 }
9794
9795 /* Save game in old style and close the file */
9796 int
9797 SaveGameOldStyle(f)
9798      FILE *f;
9799 {
9800     int i, offset;
9801     time_t tm;
9802     
9803     tm = time((time_t *) NULL);
9804     
9805     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9806     PrintOpponents(f);
9807     
9808     if (backwardMostMove > 0 || startedFromSetupPosition) {
9809         fprintf(f, "\n[--------------\n");
9810         PrintPosition(f, backwardMostMove);
9811         fprintf(f, "--------------]\n");
9812     } else {
9813         fprintf(f, "\n");
9814     }
9815
9816     i = backwardMostMove;
9817     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9818
9819     while (i < forwardMostMove) {
9820         if (commentList[i] != NULL) {
9821             fprintf(f, "[%s]\n", commentList[i]);
9822         }
9823
9824         if ((i % 2) == 1) {
9825             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
9826             i++;
9827         } else {
9828             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
9829             i++;
9830             if (commentList[i] != NULL) {
9831                 fprintf(f, "\n");
9832                 continue;
9833             }
9834             if (i >= forwardMostMove) {
9835                 fprintf(f, "\n");
9836                 break;
9837             }
9838             fprintf(f, "%s\n", parseList[i]);
9839             i++;
9840         }
9841     }
9842     
9843     if (commentList[i] != NULL) {
9844         fprintf(f, "[%s]\n", commentList[i]);
9845     }
9846
9847     /* This isn't really the old style, but it's close enough */
9848     if (gameInfo.resultDetails != NULL &&
9849         gameInfo.resultDetails[0] != NULLCHAR) {
9850         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9851                 gameInfo.resultDetails);
9852     } else {
9853         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9854     }
9855
9856     fclose(f);
9857     return TRUE;
9858 }
9859
9860 /* Save the current game to open file f and close the file */
9861 int
9862 SaveGame(f, dummy, dummy2)
9863      FILE *f;
9864      int dummy;
9865      char *dummy2;
9866 {
9867     if (gameMode == EditPosition) EditPositionDone();
9868     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9869     if (appData.oldSaveStyle)
9870       return SaveGameOldStyle(f);
9871     else
9872       return SaveGamePGN(f);
9873 }
9874
9875 /* Save the current position to the given file */
9876 int
9877 SavePositionToFile(filename)
9878      char *filename;
9879 {
9880     FILE *f;
9881     char buf[MSG_SIZ];
9882
9883     if (strcmp(filename, "-") == 0) {
9884         return SavePosition(stdout, 0, NULL);
9885     } else {
9886         f = fopen(filename, "a");
9887         if (f == NULL) {
9888             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9889             DisplayError(buf, errno);
9890             return FALSE;
9891         } else {
9892             SavePosition(f, 0, NULL);
9893             return TRUE;
9894         }
9895     }
9896 }
9897
9898 /* Save the current position to the given open file and close the file */
9899 int
9900 SavePosition(f, dummy, dummy2)
9901      FILE *f;
9902      int dummy;
9903      char *dummy2;
9904 {
9905     time_t tm;
9906     char *fen;
9907     
9908     if (appData.oldSaveStyle) {
9909         tm = time((time_t *) NULL);
9910     
9911         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9912         PrintOpponents(f);
9913         fprintf(f, "[--------------\n");
9914         PrintPosition(f, currentMove);
9915         fprintf(f, "--------------]\n");
9916     } else {
9917         fen = PositionToFEN(currentMove, NULL);
9918         fprintf(f, "%s\n", fen);
9919         free(fen);
9920     }
9921     fclose(f);
9922     return TRUE;
9923 }
9924
9925 void
9926 ReloadCmailMsgEvent(unregister)
9927      int unregister;
9928 {
9929 #if !WIN32
9930     static char *inFilename = NULL;
9931     static char *outFilename;
9932     int i;
9933     struct stat inbuf, outbuf;
9934     int status;
9935     
9936     /* Any registered moves are unregistered if unregister is set, */
9937     /* i.e. invoked by the signal handler */
9938     if (unregister) {
9939         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9940             cmailMoveRegistered[i] = FALSE;
9941             if (cmailCommentList[i] != NULL) {
9942                 free(cmailCommentList[i]);
9943                 cmailCommentList[i] = NULL;
9944             }
9945         }
9946         nCmailMovesRegistered = 0;
9947     }
9948
9949     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9950         cmailResult[i] = CMAIL_NOT_RESULT;
9951     }
9952     nCmailResults = 0;
9953
9954     if (inFilename == NULL) {
9955         /* Because the filenames are static they only get malloced once  */
9956         /* and they never get freed                                      */
9957         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9958         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9959
9960         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9961         sprintf(outFilename, "%s.out", appData.cmailGameName);
9962     }
9963     
9964     status = stat(outFilename, &outbuf);
9965     if (status < 0) {
9966         cmailMailedMove = FALSE;
9967     } else {
9968         status = stat(inFilename, &inbuf);
9969         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9970     }
9971     
9972     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9973        counts the games, notes how each one terminated, etc.
9974        
9975        It would be nice to remove this kludge and instead gather all
9976        the information while building the game list.  (And to keep it
9977        in the game list nodes instead of having a bunch of fixed-size
9978        parallel arrays.)  Note this will require getting each game's
9979        termination from the PGN tags, as the game list builder does
9980        not process the game moves.  --mann
9981        */
9982     cmailMsgLoaded = TRUE;
9983     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9984     
9985     /* Load first game in the file or popup game menu */
9986     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9987
9988 #endif /* !WIN32 */
9989     return;
9990 }
9991
9992 int
9993 RegisterMove()
9994 {
9995     FILE *f;
9996     char string[MSG_SIZ];
9997
9998     if (   cmailMailedMove
9999         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10000         return TRUE;            /* Allow free viewing  */
10001     }
10002
10003     /* Unregister move to ensure that we don't leave RegisterMove        */
10004     /* with the move registered when the conditions for registering no   */
10005     /* longer hold                                                       */
10006     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10007         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10008         nCmailMovesRegistered --;
10009
10010         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10011           {
10012               free(cmailCommentList[lastLoadGameNumber - 1]);
10013               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10014           }
10015     }
10016
10017     if (cmailOldMove == -1) {
10018         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10019         return FALSE;
10020     }
10021
10022     if (currentMove > cmailOldMove + 1) {
10023         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10024         return FALSE;
10025     }
10026
10027     if (currentMove < cmailOldMove) {
10028         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10029         return FALSE;
10030     }
10031
10032     if (forwardMostMove > currentMove) {
10033         /* Silently truncate extra moves */
10034         TruncateGame();
10035     }
10036
10037     if (   (currentMove == cmailOldMove + 1)
10038         || (   (currentMove == cmailOldMove)
10039             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10040                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10041         if (gameInfo.result != GameUnfinished) {
10042             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10043         }
10044
10045         if (commentList[currentMove] != NULL) {
10046             cmailCommentList[lastLoadGameNumber - 1]
10047               = StrSave(commentList[currentMove]);
10048         }
10049         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10050
10051         if (appData.debugMode)
10052           fprintf(debugFP, "Saving %s for game %d\n",
10053                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10054
10055         sprintf(string,
10056                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10057         
10058         f = fopen(string, "w");
10059         if (appData.oldSaveStyle) {
10060             SaveGameOldStyle(f); /* also closes the file */
10061             
10062             sprintf(string, "%s.pos.out", appData.cmailGameName);
10063             f = fopen(string, "w");
10064             SavePosition(f, 0, NULL); /* also closes the file */
10065         } else {
10066             fprintf(f, "{--------------\n");
10067             PrintPosition(f, currentMove);
10068             fprintf(f, "--------------}\n\n");
10069             
10070             SaveGame(f, 0, NULL); /* also closes the file*/
10071         }
10072         
10073         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10074         nCmailMovesRegistered ++;
10075     } else if (nCmailGames == 1) {
10076         DisplayError(_("You have not made a move yet"), 0);
10077         return FALSE;
10078     }
10079
10080     return TRUE;
10081 }
10082
10083 void
10084 MailMoveEvent()
10085 {
10086 #if !WIN32
10087     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10088     FILE *commandOutput;
10089     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10090     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10091     int nBuffers;
10092     int i;
10093     int archived;
10094     char *arcDir;
10095
10096     if (! cmailMsgLoaded) {
10097         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10098         return;
10099     }
10100
10101     if (nCmailGames == nCmailResults) {
10102         DisplayError(_("No unfinished games"), 0);
10103         return;
10104     }
10105
10106 #if CMAIL_PROHIBIT_REMAIL
10107     if (cmailMailedMove) {
10108         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);
10109         DisplayError(msg, 0);
10110         return;
10111     }
10112 #endif
10113
10114     if (! (cmailMailedMove || RegisterMove())) return;
10115     
10116     if (   cmailMailedMove
10117         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10118         sprintf(string, partCommandString,
10119                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10120         commandOutput = popen(string, "r");
10121
10122         if (commandOutput == NULL) {
10123             DisplayError(_("Failed to invoke cmail"), 0);
10124         } else {
10125             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10126                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10127             }
10128             if (nBuffers > 1) {
10129                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10130                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10131                 nBytes = MSG_SIZ - 1;
10132             } else {
10133                 (void) memcpy(msg, buffer, nBytes);
10134             }
10135             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10136
10137             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10138                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10139
10140                 archived = TRUE;
10141                 for (i = 0; i < nCmailGames; i ++) {
10142                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10143                         archived = FALSE;
10144                     }
10145                 }
10146                 if (   archived
10147                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10148                         != NULL)) {
10149                     sprintf(buffer, "%s/%s.%s.archive",
10150                             arcDir,
10151                             appData.cmailGameName,
10152                             gameInfo.date);
10153                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10154                     cmailMsgLoaded = FALSE;
10155                 }
10156             }
10157
10158             DisplayInformation(msg);
10159             pclose(commandOutput);
10160         }
10161     } else {
10162         if ((*cmailMsg) != '\0') {
10163             DisplayInformation(cmailMsg);
10164         }
10165     }
10166
10167     return;
10168 #endif /* !WIN32 */
10169 }
10170
10171 char *
10172 CmailMsg()
10173 {
10174 #if WIN32
10175     return NULL;
10176 #else
10177     int  prependComma = 0;
10178     char number[5];
10179     char string[MSG_SIZ];       /* Space for game-list */
10180     int  i;
10181     
10182     if (!cmailMsgLoaded) return "";
10183
10184     if (cmailMailedMove) {
10185         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10186     } else {
10187         /* Create a list of games left */
10188         sprintf(string, "[");
10189         for (i = 0; i < nCmailGames; i ++) {
10190             if (! (   cmailMoveRegistered[i]
10191                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10192                 if (prependComma) {
10193                     sprintf(number, ",%d", i + 1);
10194                 } else {
10195                     sprintf(number, "%d", i + 1);
10196                     prependComma = 1;
10197                 }
10198                 
10199                 strcat(string, number);
10200             }
10201         }
10202         strcat(string, "]");
10203
10204         if (nCmailMovesRegistered + nCmailResults == 0) {
10205             switch (nCmailGames) {
10206               case 1:
10207                 sprintf(cmailMsg,
10208                         _("Still need to make move for game\n"));
10209                 break;
10210                 
10211               case 2:
10212                 sprintf(cmailMsg,
10213                         _("Still need to make moves for both games\n"));
10214                 break;
10215                 
10216               default:
10217                 sprintf(cmailMsg,
10218                         _("Still need to make moves for all %d games\n"),
10219                         nCmailGames);
10220                 break;
10221             }
10222         } else {
10223             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10224               case 1:
10225                 sprintf(cmailMsg,
10226                         _("Still need to make a move for game %s\n"),
10227                         string);
10228                 break;
10229                 
10230               case 0:
10231                 if (nCmailResults == nCmailGames) {
10232                     sprintf(cmailMsg, _("No unfinished games\n"));
10233                 } else {
10234                     sprintf(cmailMsg, _("Ready to send mail\n"));
10235                 }
10236                 break;
10237                 
10238               default:
10239                 sprintf(cmailMsg,
10240                         _("Still need to make moves for games %s\n"),
10241                         string);
10242             }
10243         }
10244     }
10245     return cmailMsg;
10246 #endif /* WIN32 */
10247 }
10248
10249 void
10250 ResetGameEvent()
10251 {
10252     if (gameMode == Training)
10253       SetTrainingModeOff();
10254
10255     Reset(TRUE, TRUE);
10256     cmailMsgLoaded = FALSE;
10257     if (appData.icsActive) {
10258       SendToICS(ics_prefix);
10259       SendToICS("refresh\n");
10260     }
10261 }
10262
10263 void
10264 ExitEvent(status)
10265      int status;
10266 {
10267     exiting++;
10268     if (exiting > 2) {
10269       /* Give up on clean exit */
10270       exit(status);
10271     }
10272     if (exiting > 1) {
10273       /* Keep trying for clean exit */
10274       return;
10275     }
10276
10277     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10278
10279     if (telnetISR != NULL) {
10280       RemoveInputSource(telnetISR);
10281     }
10282     if (icsPR != NoProc) {
10283       DestroyChildProcess(icsPR, TRUE);
10284     }
10285 #if 0
10286     /* Save game if resource set and not already saved by GameEnds() */
10287     if ((gameInfo.resultDetails == NULL || errorExitFlag )
10288                              && forwardMostMove > 0) {
10289       if (*appData.saveGameFile != NULLCHAR) {
10290         SaveGameToFile(appData.saveGameFile, TRUE);
10291       } else if (appData.autoSaveGames) {
10292         AutoSaveGame();
10293       }
10294       if (*appData.savePositionFile != NULLCHAR) {
10295         SavePositionToFile(appData.savePositionFile);
10296       }
10297     }
10298     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10299 #else
10300     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10301     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10302 #endif
10303     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10304     /* make sure this other one finishes before killing it!                  */
10305     if(endingGame) { int count = 0;
10306         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10307         while(endingGame && count++ < 10) DoSleep(1);
10308         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10309     }
10310
10311     /* Kill off chess programs */
10312     if (first.pr != NoProc) {
10313         ExitAnalyzeMode();
10314         
10315         DoSleep( appData.delayBeforeQuit );
10316         SendToProgram("quit\n", &first);
10317         DoSleep( appData.delayAfterQuit );
10318         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10319     }
10320     if (second.pr != NoProc) {
10321         DoSleep( appData.delayBeforeQuit );
10322         SendToProgram("quit\n", &second);
10323         DoSleep( appData.delayAfterQuit );
10324         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10325     }
10326     if (first.isr != NULL) {
10327         RemoveInputSource(first.isr);
10328     }
10329     if (second.isr != NULL) {
10330         RemoveInputSource(second.isr);
10331     }
10332
10333     ShutDownFrontEnd();
10334     exit(status);
10335 }
10336
10337 void
10338 PauseEvent()
10339 {
10340     if (appData.debugMode)
10341         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10342     if (pausing) {
10343         pausing = FALSE;
10344         ModeHighlight();
10345         if (gameMode == MachinePlaysWhite ||
10346             gameMode == MachinePlaysBlack) {
10347             StartClocks();
10348         } else {
10349             DisplayBothClocks();
10350         }
10351         if (gameMode == PlayFromGameFile) {
10352             if (appData.timeDelay >= 0) 
10353                 AutoPlayGameLoop();
10354         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10355             Reset(FALSE, TRUE);
10356             SendToICS(ics_prefix);
10357             SendToICS("refresh\n");
10358         } else if (currentMove < forwardMostMove) {
10359             ForwardInner(forwardMostMove);
10360         }
10361         pauseExamInvalid = FALSE;
10362     } else {
10363         switch (gameMode) {
10364           default:
10365             return;
10366           case IcsExamining:
10367             pauseExamForwardMostMove = forwardMostMove;
10368             pauseExamInvalid = FALSE;
10369             /* fall through */
10370           case IcsObserving:
10371           case IcsPlayingWhite:
10372           case IcsPlayingBlack:
10373             pausing = TRUE;
10374             ModeHighlight();
10375             return;
10376           case PlayFromGameFile:
10377             (void) StopLoadGameTimer();
10378             pausing = TRUE;
10379             ModeHighlight();
10380             break;
10381           case BeginningOfGame:
10382             if (appData.icsActive) return;
10383             /* else fall through */
10384           case MachinePlaysWhite:
10385           case MachinePlaysBlack:
10386           case TwoMachinesPlay:
10387             if (forwardMostMove == 0)
10388               return;           /* don't pause if no one has moved */
10389             if ((gameMode == MachinePlaysWhite &&
10390                  !WhiteOnMove(forwardMostMove)) ||
10391                 (gameMode == MachinePlaysBlack &&
10392                  WhiteOnMove(forwardMostMove))) {
10393                 StopClocks();
10394             }
10395             pausing = TRUE;
10396             ModeHighlight();
10397             break;
10398         }
10399     }
10400 }
10401
10402 void
10403 EditCommentEvent()
10404 {
10405     char title[MSG_SIZ];
10406
10407     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10408         strcpy(title, _("Edit comment"));
10409     } else {
10410         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10411                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10412                 parseList[currentMove - 1]);
10413     }
10414
10415     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10416 }
10417
10418
10419 void
10420 EditTagsEvent()
10421 {
10422     char *tags = PGNTags(&gameInfo);
10423     EditTagsPopUp(tags);
10424     free(tags);
10425 }
10426
10427 void
10428 AnalyzeModeEvent()
10429 {
10430     if (appData.noChessProgram || gameMode == AnalyzeMode)
10431       return;
10432
10433     if (gameMode != AnalyzeFile) {
10434         if (!appData.icsEngineAnalyze) {
10435                EditGameEvent();
10436                if (gameMode != EditGame) return;
10437         }
10438         ResurrectChessProgram();
10439         SendToProgram("analyze\n", &first);
10440         first.analyzing = TRUE;
10441         /*first.maybeThinking = TRUE;*/
10442         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10443         AnalysisPopUp(_("Analysis"),
10444                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10445     }
10446     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10447     pausing = FALSE;
10448     ModeHighlight();
10449     SetGameInfo();
10450
10451     StartAnalysisClock();
10452     GetTimeMark(&lastNodeCountTime);
10453     lastNodeCount = 0;
10454 }
10455
10456 void
10457 AnalyzeFileEvent()
10458 {
10459     if (appData.noChessProgram || gameMode == AnalyzeFile)
10460       return;
10461
10462     if (gameMode != AnalyzeMode) {
10463         EditGameEvent();
10464         if (gameMode != EditGame) return;
10465         ResurrectChessProgram();
10466         SendToProgram("analyze\n", &first);
10467         first.analyzing = TRUE;
10468         /*first.maybeThinking = TRUE;*/
10469         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10470         AnalysisPopUp(_("Analysis"),
10471                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10472     }
10473     gameMode = AnalyzeFile;
10474     pausing = FALSE;
10475     ModeHighlight();
10476     SetGameInfo();
10477
10478     StartAnalysisClock();
10479     GetTimeMark(&lastNodeCountTime);
10480     lastNodeCount = 0;
10481 }
10482
10483 void
10484 MachineWhiteEvent()
10485 {
10486     char buf[MSG_SIZ];
10487     char *bookHit = NULL;
10488
10489     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10490       return;
10491
10492
10493     if (gameMode == PlayFromGameFile || 
10494         gameMode == TwoMachinesPlay  || 
10495         gameMode == Training         || 
10496         gameMode == AnalyzeMode      || 
10497         gameMode == EndOfGame)
10498         EditGameEvent();
10499
10500     if (gameMode == EditPosition) 
10501         EditPositionDone();
10502
10503     if (!WhiteOnMove(currentMove)) {
10504         DisplayError(_("It is not White's turn"), 0);
10505         return;
10506     }
10507   
10508     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10509       ExitAnalyzeMode();
10510
10511     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10512         gameMode == AnalyzeFile)
10513         TruncateGame();
10514
10515     ResurrectChessProgram();    /* in case it isn't running */
10516     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10517         gameMode = MachinePlaysWhite;
10518         ResetClocks();
10519     } else
10520     gameMode = MachinePlaysWhite;
10521     pausing = FALSE;
10522     ModeHighlight();
10523     SetGameInfo();
10524     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10525     DisplayTitle(buf);
10526     if (first.sendName) {
10527       sprintf(buf, "name %s\n", gameInfo.black);
10528       SendToProgram(buf, &first);
10529     }
10530     if (first.sendTime) {
10531       if (first.useColors) {
10532         SendToProgram("black\n", &first); /*gnu kludge*/
10533       }
10534       SendTimeRemaining(&first, TRUE);
10535     }
10536     if (first.useColors) {
10537       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10538     }
10539     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10540     SetMachineThinkingEnables();
10541     first.maybeThinking = TRUE;
10542     StartClocks();
10543
10544     if (appData.autoFlipView && !flipView) {
10545       flipView = !flipView;
10546       DrawPosition(FALSE, NULL);
10547       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10548     }
10549
10550     if(bookHit) { // [HGM] book: simulate book reply
10551         static char bookMove[MSG_SIZ]; // a bit generous?
10552
10553         programStats.nodes = programStats.depth = programStats.time = 
10554         programStats.score = programStats.got_only_move = 0;
10555         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10556
10557         strcpy(bookMove, "move ");
10558         strcat(bookMove, bookHit);
10559         HandleMachineMove(bookMove, &first);
10560     }
10561 }
10562
10563 void
10564 MachineBlackEvent()
10565 {
10566     char buf[MSG_SIZ];
10567    char *bookHit = NULL;
10568
10569     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10570         return;
10571
10572
10573     if (gameMode == PlayFromGameFile || 
10574         gameMode == TwoMachinesPlay  || 
10575         gameMode == Training         || 
10576         gameMode == AnalyzeMode      || 
10577         gameMode == EndOfGame)
10578         EditGameEvent();
10579
10580     if (gameMode == EditPosition) 
10581         EditPositionDone();
10582
10583     if (WhiteOnMove(currentMove)) {
10584         DisplayError(_("It is not Black's turn"), 0);
10585         return;
10586     }
10587     
10588     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10589       ExitAnalyzeMode();
10590
10591     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10592         gameMode == AnalyzeFile)
10593         TruncateGame();
10594
10595     ResurrectChessProgram();    /* in case it isn't running */
10596     gameMode = MachinePlaysBlack;
10597     pausing = FALSE;
10598     ModeHighlight();
10599     SetGameInfo();
10600     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10601     DisplayTitle(buf);
10602     if (first.sendName) {
10603       sprintf(buf, "name %s\n", gameInfo.white);
10604       SendToProgram(buf, &first);
10605     }
10606     if (first.sendTime) {
10607       if (first.useColors) {
10608         SendToProgram("white\n", &first); /*gnu kludge*/
10609       }
10610       SendTimeRemaining(&first, FALSE);
10611     }
10612     if (first.useColors) {
10613       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10614     }
10615     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10616     SetMachineThinkingEnables();
10617     first.maybeThinking = TRUE;
10618     StartClocks();
10619
10620     if (appData.autoFlipView && flipView) {
10621       flipView = !flipView;
10622       DrawPosition(FALSE, NULL);
10623       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10624     }
10625     if(bookHit) { // [HGM] book: simulate book reply
10626         static char bookMove[MSG_SIZ]; // a bit generous?
10627
10628         programStats.nodes = programStats.depth = programStats.time = 
10629         programStats.score = programStats.got_only_move = 0;
10630         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10631
10632         strcpy(bookMove, "move ");
10633         strcat(bookMove, bookHit);
10634         HandleMachineMove(bookMove, &first);
10635     }
10636 }
10637
10638
10639 void
10640 DisplayTwoMachinesTitle()
10641 {
10642     char buf[MSG_SIZ];
10643     if (appData.matchGames > 0) {
10644         if (first.twoMachinesColor[0] == 'w') {
10645             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10646                     gameInfo.white, gameInfo.black,
10647                     first.matchWins, second.matchWins,
10648                     matchGame - 1 - (first.matchWins + second.matchWins));
10649         } else {
10650             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10651                     gameInfo.white, gameInfo.black,
10652                     second.matchWins, first.matchWins,
10653                     matchGame - 1 - (first.matchWins + second.matchWins));
10654         }
10655     } else {
10656         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10657     }
10658     DisplayTitle(buf);
10659 }
10660
10661 void
10662 TwoMachinesEvent P((void))
10663 {
10664     int i;
10665     char buf[MSG_SIZ];
10666     ChessProgramState *onmove;
10667     char *bookHit = NULL;
10668     
10669     if (appData.noChessProgram) return;
10670
10671     switch (gameMode) {
10672       case TwoMachinesPlay:
10673         return;
10674       case MachinePlaysWhite:
10675       case MachinePlaysBlack:
10676         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10677             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10678             return;
10679         }
10680         /* fall through */
10681       case BeginningOfGame:
10682       case PlayFromGameFile:
10683       case EndOfGame:
10684         EditGameEvent();
10685         if (gameMode != EditGame) return;
10686         break;
10687       case EditPosition:
10688         EditPositionDone();
10689         break;
10690       case AnalyzeMode:
10691       case AnalyzeFile:
10692         ExitAnalyzeMode();
10693         break;
10694       case EditGame:
10695       default:
10696         break;
10697     }
10698
10699     forwardMostMove = currentMove;
10700     ResurrectChessProgram();    /* in case first program isn't running */
10701
10702     if (second.pr == NULL) {
10703         StartChessProgram(&second);
10704         if (second.protocolVersion == 1) {
10705           TwoMachinesEventIfReady();
10706         } else {
10707           /* kludge: allow timeout for initial "feature" command */
10708           FreezeUI();
10709           DisplayMessage("", _("Starting second chess program"));
10710           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10711         }
10712         return;
10713     }
10714     DisplayMessage("", "");
10715     InitChessProgram(&second, FALSE);
10716     SendToProgram("force\n", &second);
10717     if (startedFromSetupPosition) {
10718         SendBoard(&second, backwardMostMove);
10719     if (appData.debugMode) {
10720         fprintf(debugFP, "Two Machines\n");
10721     }
10722     }
10723     for (i = backwardMostMove; i < forwardMostMove; i++) {
10724         SendMoveToProgram(i, &second);
10725     }
10726
10727     gameMode = TwoMachinesPlay;
10728     pausing = FALSE;
10729     ModeHighlight();
10730     SetGameInfo();
10731     DisplayTwoMachinesTitle();
10732     firstMove = TRUE;
10733     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10734         onmove = &first;
10735     } else {
10736         onmove = &second;
10737     }
10738
10739     SendToProgram(first.computerString, &first);
10740     if (first.sendName) {
10741       sprintf(buf, "name %s\n", second.tidy);
10742       SendToProgram(buf, &first);
10743     }
10744     SendToProgram(second.computerString, &second);
10745     if (second.sendName) {
10746       sprintf(buf, "name %s\n", first.tidy);
10747       SendToProgram(buf, &second);
10748     }
10749
10750     ResetClocks();
10751     if (!first.sendTime || !second.sendTime) {
10752         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10753         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10754     }
10755     if (onmove->sendTime) {
10756       if (onmove->useColors) {
10757         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10758       }
10759       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10760     }
10761     if (onmove->useColors) {
10762       SendToProgram(onmove->twoMachinesColor, onmove);
10763     }
10764     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10765 //    SendToProgram("go\n", onmove);
10766     onmove->maybeThinking = TRUE;
10767     SetMachineThinkingEnables();
10768
10769     StartClocks();
10770
10771     if(bookHit) { // [HGM] book: simulate book reply
10772         static char bookMove[MSG_SIZ]; // a bit generous?
10773
10774         programStats.nodes = programStats.depth = programStats.time = 
10775         programStats.score = programStats.got_only_move = 0;
10776         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10777
10778         strcpy(bookMove, "move ");
10779         strcat(bookMove, bookHit);
10780         HandleMachineMove(bookMove, &first);
10781     }
10782 }
10783
10784 void
10785 TrainingEvent()
10786 {
10787     if (gameMode == Training) {
10788       SetTrainingModeOff();
10789       gameMode = PlayFromGameFile;
10790       DisplayMessage("", _("Training mode off"));
10791     } else {
10792       gameMode = Training;
10793       animateTraining = appData.animate;
10794
10795       /* make sure we are not already at the end of the game */
10796       if (currentMove < forwardMostMove) {
10797         SetTrainingModeOn();
10798         DisplayMessage("", _("Training mode on"));
10799       } else {
10800         gameMode = PlayFromGameFile;
10801         DisplayError(_("Already at end of game"), 0);
10802       }
10803     }
10804     ModeHighlight();
10805 }
10806
10807 void
10808 IcsClientEvent()
10809 {
10810     if (!appData.icsActive) return;
10811     switch (gameMode) {
10812       case IcsPlayingWhite:
10813       case IcsPlayingBlack:
10814       case IcsObserving:
10815       case IcsIdle:
10816       case BeginningOfGame:
10817       case IcsExamining:
10818         return;
10819
10820       case EditGame:
10821         break;
10822
10823       case EditPosition:
10824         EditPositionDone();
10825         break;
10826
10827       case AnalyzeMode:
10828       case AnalyzeFile:
10829         ExitAnalyzeMode();
10830         break;
10831         
10832       default:
10833         EditGameEvent();
10834         break;
10835     }
10836
10837     gameMode = IcsIdle;
10838     ModeHighlight();
10839     return;
10840 }
10841
10842
10843 void
10844 EditGameEvent()
10845 {
10846     int i;
10847
10848     switch (gameMode) {
10849       case Training:
10850         SetTrainingModeOff();
10851         break;
10852       case MachinePlaysWhite:
10853       case MachinePlaysBlack:
10854       case BeginningOfGame:
10855         SendToProgram("force\n", &first);
10856         SetUserThinkingEnables();
10857         break;
10858       case PlayFromGameFile:
10859         (void) StopLoadGameTimer();
10860         if (gameFileFP != NULL) {
10861             gameFileFP = NULL;
10862         }
10863         break;
10864       case EditPosition:
10865         EditPositionDone();
10866         break;
10867       case AnalyzeMode:
10868       case AnalyzeFile:
10869         ExitAnalyzeMode();
10870         SendToProgram("force\n", &first);
10871         break;
10872       case TwoMachinesPlay:
10873         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10874         ResurrectChessProgram();
10875         SetUserThinkingEnables();
10876         break;
10877       case EndOfGame:
10878         ResurrectChessProgram();
10879         break;
10880       case IcsPlayingBlack:
10881       case IcsPlayingWhite:
10882         DisplayError(_("Warning: You are still playing a game"), 0);
10883         break;
10884       case IcsObserving:
10885         DisplayError(_("Warning: You are still observing a game"), 0);
10886         break;
10887       case IcsExamining:
10888         DisplayError(_("Warning: You are still examining a game"), 0);
10889         break;
10890       case IcsIdle:
10891         break;
10892       case EditGame:
10893       default:
10894         return;
10895     }
10896     
10897     pausing = FALSE;
10898     StopClocks();
10899     first.offeredDraw = second.offeredDraw = 0;
10900
10901     if (gameMode == PlayFromGameFile) {
10902         whiteTimeRemaining = timeRemaining[0][currentMove];
10903         blackTimeRemaining = timeRemaining[1][currentMove];
10904         DisplayTitle("");
10905     }
10906
10907     if (gameMode == MachinePlaysWhite ||
10908         gameMode == MachinePlaysBlack ||
10909         gameMode == TwoMachinesPlay ||
10910         gameMode == EndOfGame) {
10911         i = forwardMostMove;
10912         while (i > currentMove) {
10913             SendToProgram("undo\n", &first);
10914             i--;
10915         }
10916         whiteTimeRemaining = timeRemaining[0][currentMove];
10917         blackTimeRemaining = timeRemaining[1][currentMove];
10918         DisplayBothClocks();
10919         if (whiteFlag || blackFlag) {
10920             whiteFlag = blackFlag = 0;
10921         }
10922         DisplayTitle("");
10923     }           
10924     
10925     gameMode = EditGame;
10926     ModeHighlight();
10927     SetGameInfo();
10928 }
10929
10930
10931 void
10932 EditPositionEvent()
10933 {
10934     if (gameMode == EditPosition) {
10935         EditGameEvent();
10936         return;
10937     }
10938     
10939     EditGameEvent();
10940     if (gameMode != EditGame) return;
10941     
10942     gameMode = EditPosition;
10943     ModeHighlight();
10944     SetGameInfo();
10945     if (currentMove > 0)
10946       CopyBoard(boards[0], boards[currentMove]);
10947     
10948     blackPlaysFirst = !WhiteOnMove(currentMove);
10949     ResetClocks();
10950     currentMove = forwardMostMove = backwardMostMove = 0;
10951     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10952     DisplayMove(-1);
10953 }
10954
10955 void
10956 ExitAnalyzeMode()
10957 {
10958     /* [DM] icsEngineAnalyze - possible call from other functions */
10959     if (appData.icsEngineAnalyze) {
10960         appData.icsEngineAnalyze = FALSE;
10961
10962         DisplayMessage("",_("Close ICS engine analyze..."));
10963     }
10964     if (first.analysisSupport && first.analyzing) {
10965       SendToProgram("exit\n", &first);
10966       first.analyzing = FALSE;
10967     }
10968     AnalysisPopDown();
10969     thinkOutput[0] = NULLCHAR;
10970 }
10971
10972 void
10973 EditPositionDone()
10974 {
10975     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
10976
10977     startedFromSetupPosition = TRUE;
10978     InitChessProgram(&first, FALSE);
10979     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
10980     if(boards[0][0][BOARD_WIDTH>>1] == king) {
10981         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
10982         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
10983     } else castlingRights[0][2] = -1;
10984     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
10985         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
10986         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
10987     } else castlingRights[0][5] = -1;
10988     SendToProgram("force\n", &first);
10989     if (blackPlaysFirst) {
10990         strcpy(moveList[0], "");
10991         strcpy(parseList[0], "");
10992         currentMove = forwardMostMove = backwardMostMove = 1;
10993         CopyBoard(boards[1], boards[0]);
10994         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10995         { int i;
10996           epStatus[1] = epStatus[0];
10997           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10998         }
10999     } else {
11000         currentMove = forwardMostMove = backwardMostMove = 0;
11001     }
11002     SendBoard(&first, forwardMostMove);
11003     if (appData.debugMode) {
11004         fprintf(debugFP, "EditPosDone\n");
11005     }
11006     DisplayTitle("");
11007     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11008     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11009     gameMode = EditGame;
11010     ModeHighlight();
11011     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11012     ClearHighlights(); /* [AS] */
11013 }
11014
11015 /* Pause for `ms' milliseconds */
11016 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11017 void
11018 TimeDelay(ms)
11019      long ms;
11020 {
11021     TimeMark m1, m2;
11022
11023     GetTimeMark(&m1);
11024     do {
11025         GetTimeMark(&m2);
11026     } while (SubtractTimeMarks(&m2, &m1) < ms);
11027 }
11028
11029 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11030 void
11031 SendMultiLineToICS(buf)
11032      char *buf;
11033 {
11034     char temp[MSG_SIZ+1], *p;
11035     int len;
11036
11037     len = strlen(buf);
11038     if (len > MSG_SIZ)
11039       len = MSG_SIZ;
11040   
11041     strncpy(temp, buf, len);
11042     temp[len] = 0;
11043
11044     p = temp;
11045     while (*p) {
11046         if (*p == '\n' || *p == '\r')
11047           *p = ' ';
11048         ++p;
11049     }
11050
11051     strcat(temp, "\n");
11052     SendToICS(temp);
11053     SendToPlayer(temp, strlen(temp));
11054 }
11055
11056 void
11057 SetWhiteToPlayEvent()
11058 {
11059     if (gameMode == EditPosition) {
11060         blackPlaysFirst = FALSE;
11061         DisplayBothClocks();    /* works because currentMove is 0 */
11062     } else if (gameMode == IcsExamining) {
11063         SendToICS(ics_prefix);
11064         SendToICS("tomove white\n");
11065     }
11066 }
11067
11068 void
11069 SetBlackToPlayEvent()
11070 {
11071     if (gameMode == EditPosition) {
11072         blackPlaysFirst = TRUE;
11073         currentMove = 1;        /* kludge */
11074         DisplayBothClocks();
11075         currentMove = 0;
11076     } else if (gameMode == IcsExamining) {
11077         SendToICS(ics_prefix);
11078         SendToICS("tomove black\n");
11079     }
11080 }
11081
11082 void
11083 EditPositionMenuEvent(selection, x, y)
11084      ChessSquare selection;
11085      int x, y;
11086 {
11087     char buf[MSG_SIZ];
11088     ChessSquare piece = boards[0][y][x];
11089
11090     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11091
11092     switch (selection) {
11093       case ClearBoard:
11094         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11095             SendToICS(ics_prefix);
11096             SendToICS("bsetup clear\n");
11097         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11098             SendToICS(ics_prefix);
11099             SendToICS("clearboard\n");
11100         } else {
11101             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11102                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11103                 for (y = 0; y < BOARD_HEIGHT; y++) {
11104                     if (gameMode == IcsExamining) {
11105                         if (boards[currentMove][y][x] != EmptySquare) {
11106                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11107                                     AAA + x, ONE + y);
11108                             SendToICS(buf);
11109                         }
11110                     } else {
11111                         boards[0][y][x] = p;
11112                     }
11113                 }
11114             }
11115         }
11116         if (gameMode == EditPosition) {
11117             DrawPosition(FALSE, boards[0]);
11118         }
11119         break;
11120
11121       case WhitePlay:
11122         SetWhiteToPlayEvent();
11123         break;
11124
11125       case BlackPlay:
11126         SetBlackToPlayEvent();
11127         break;
11128
11129       case EmptySquare:
11130         if (gameMode == IcsExamining) {
11131             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11132             SendToICS(buf);
11133         } else {
11134             boards[0][y][x] = EmptySquare;
11135             DrawPosition(FALSE, boards[0]);
11136         }
11137         break;
11138
11139       case PromotePiece:
11140         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11141            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11142             selection = (ChessSquare) (PROMOTED piece);
11143         } else if(piece == EmptySquare) selection = WhiteSilver;
11144         else selection = (ChessSquare)((int)piece - 1);
11145         goto defaultlabel;
11146
11147       case DemotePiece:
11148         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11149            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11150             selection = (ChessSquare) (DEMOTED piece);
11151         } else if(piece == EmptySquare) selection = BlackSilver;
11152         else selection = (ChessSquare)((int)piece + 1);       
11153         goto defaultlabel;
11154
11155       case WhiteQueen:
11156       case BlackQueen:
11157         if(gameInfo.variant == VariantShatranj ||
11158            gameInfo.variant == VariantXiangqi  ||
11159            gameInfo.variant == VariantCourier    )
11160             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11161         goto defaultlabel;
11162
11163       case WhiteKing:
11164       case BlackKing:
11165         if(gameInfo.variant == VariantXiangqi)
11166             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11167         if(gameInfo.variant == VariantKnightmate)
11168             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11169       default:
11170         defaultlabel:
11171         if (gameMode == IcsExamining) {
11172             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11173                     PieceToChar(selection), AAA + x, ONE + y);
11174             SendToICS(buf);
11175         } else {
11176             boards[0][y][x] = selection;
11177             DrawPosition(FALSE, boards[0]);
11178         }
11179         break;
11180     }
11181 }
11182
11183
11184 void
11185 DropMenuEvent(selection, x, y)
11186      ChessSquare selection;
11187      int x, y;
11188 {
11189     ChessMove moveType;
11190
11191     switch (gameMode) {
11192       case IcsPlayingWhite:
11193       case MachinePlaysBlack:
11194         if (!WhiteOnMove(currentMove)) {
11195             DisplayMoveError(_("It is Black's turn"));
11196             return;
11197         }
11198         moveType = WhiteDrop;
11199         break;
11200       case IcsPlayingBlack:
11201       case MachinePlaysWhite:
11202         if (WhiteOnMove(currentMove)) {
11203             DisplayMoveError(_("It is White's turn"));
11204             return;
11205         }
11206         moveType = BlackDrop;
11207         break;
11208       case EditGame:
11209         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11210         break;
11211       default:
11212         return;
11213     }
11214
11215     if (moveType == BlackDrop && selection < BlackPawn) {
11216       selection = (ChessSquare) ((int) selection
11217                                  + (int) BlackPawn - (int) WhitePawn);
11218     }
11219     if (boards[currentMove][y][x] != EmptySquare) {
11220         DisplayMoveError(_("That square is occupied"));
11221         return;
11222     }
11223
11224     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11225 }
11226
11227 void
11228 AcceptEvent()
11229 {
11230     /* Accept a pending offer of any kind from opponent */
11231     
11232     if (appData.icsActive) {
11233         SendToICS(ics_prefix);
11234         SendToICS("accept\n");
11235     } else if (cmailMsgLoaded) {
11236         if (currentMove == cmailOldMove &&
11237             commentList[cmailOldMove] != NULL &&
11238             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11239                    "Black offers a draw" : "White offers a draw")) {
11240             TruncateGame();
11241             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11242             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11243         } else {
11244             DisplayError(_("There is no pending offer on this move"), 0);
11245             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11246         }
11247     } else {
11248         /* Not used for offers from chess program */
11249     }
11250 }
11251
11252 void
11253 DeclineEvent()
11254 {
11255     /* Decline a pending offer of any kind from opponent */
11256     
11257     if (appData.icsActive) {
11258         SendToICS(ics_prefix);
11259         SendToICS("decline\n");
11260     } else if (cmailMsgLoaded) {
11261         if (currentMove == cmailOldMove &&
11262             commentList[cmailOldMove] != NULL &&
11263             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11264                    "Black offers a draw" : "White offers a draw")) {
11265 #ifdef NOTDEF
11266             AppendComment(cmailOldMove, "Draw declined");
11267             DisplayComment(cmailOldMove - 1, "Draw declined");
11268 #endif /*NOTDEF*/
11269         } else {
11270             DisplayError(_("There is no pending offer on this move"), 0);
11271         }
11272     } else {
11273         /* Not used for offers from chess program */
11274     }
11275 }
11276
11277 void
11278 RematchEvent()
11279 {
11280     /* Issue ICS rematch command */
11281     if (appData.icsActive) {
11282         SendToICS(ics_prefix);
11283         SendToICS("rematch\n");
11284     }
11285 }
11286
11287 void
11288 CallFlagEvent()
11289 {
11290     /* Call your opponent's flag (claim a win on time) */
11291     if (appData.icsActive) {
11292         SendToICS(ics_prefix);
11293         SendToICS("flag\n");
11294     } else {
11295         switch (gameMode) {
11296           default:
11297             return;
11298           case MachinePlaysWhite:
11299             if (whiteFlag) {
11300                 if (blackFlag)
11301                   GameEnds(GameIsDrawn, "Both players ran out of time",
11302                            GE_PLAYER);
11303                 else
11304                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11305             } else {
11306                 DisplayError(_("Your opponent is not out of time"), 0);
11307             }
11308             break;
11309           case MachinePlaysBlack:
11310             if (blackFlag) {
11311                 if (whiteFlag)
11312                   GameEnds(GameIsDrawn, "Both players ran out of time",
11313                            GE_PLAYER);
11314                 else
11315                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11316             } else {
11317                 DisplayError(_("Your opponent is not out of time"), 0);
11318             }
11319             break;
11320         }
11321     }
11322 }
11323
11324 void
11325 DrawEvent()
11326 {
11327     /* Offer draw or accept pending draw offer from opponent */
11328     
11329     if (appData.icsActive) {
11330         /* Note: tournament rules require draw offers to be
11331            made after you make your move but before you punch
11332            your clock.  Currently ICS doesn't let you do that;
11333            instead, you immediately punch your clock after making
11334            a move, but you can offer a draw at any time. */
11335         
11336         SendToICS(ics_prefix);
11337         SendToICS("draw\n");
11338     } else if (cmailMsgLoaded) {
11339         if (currentMove == cmailOldMove &&
11340             commentList[cmailOldMove] != NULL &&
11341             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11342                    "Black offers a draw" : "White offers a draw")) {
11343             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11344             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11345         } else if (currentMove == cmailOldMove + 1) {
11346             char *offer = WhiteOnMove(cmailOldMove) ?
11347               "White offers a draw" : "Black offers a draw";
11348             AppendComment(currentMove, offer);
11349             DisplayComment(currentMove - 1, offer);
11350             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11351         } else {
11352             DisplayError(_("You must make your move before offering a draw"), 0);
11353             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11354         }
11355     } else if (first.offeredDraw) {
11356         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11357     } else {
11358         if (first.sendDrawOffers) {
11359             SendToProgram("draw\n", &first);
11360             userOfferedDraw = TRUE;
11361         }
11362     }
11363 }
11364
11365 void
11366 AdjournEvent()
11367 {
11368     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11369     
11370     if (appData.icsActive) {
11371         SendToICS(ics_prefix);
11372         SendToICS("adjourn\n");
11373     } else {
11374         /* Currently GNU Chess doesn't offer or accept Adjourns */
11375     }
11376 }
11377
11378
11379 void
11380 AbortEvent()
11381 {
11382     /* Offer Abort or accept pending Abort offer from opponent */
11383     
11384     if (appData.icsActive) {
11385         SendToICS(ics_prefix);
11386         SendToICS("abort\n");
11387     } else {
11388         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11389     }
11390 }
11391
11392 void
11393 ResignEvent()
11394 {
11395     /* Resign.  You can do this even if it's not your turn. */
11396     
11397     if (appData.icsActive) {
11398         SendToICS(ics_prefix);
11399         SendToICS("resign\n");
11400     } else {
11401         switch (gameMode) {
11402           case MachinePlaysWhite:
11403             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11404             break;
11405           case MachinePlaysBlack:
11406             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11407             break;
11408           case EditGame:
11409             if (cmailMsgLoaded) {
11410                 TruncateGame();
11411                 if (WhiteOnMove(cmailOldMove)) {
11412                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11413                 } else {
11414                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11415                 }
11416                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11417             }
11418             break;
11419           default:
11420             break;
11421         }
11422     }
11423 }
11424
11425
11426 void
11427 StopObservingEvent()
11428 {
11429     /* Stop observing current games */
11430     SendToICS(ics_prefix);
11431     SendToICS("unobserve\n");
11432 }
11433
11434 void
11435 StopExaminingEvent()
11436 {
11437     /* Stop observing current game */
11438     SendToICS(ics_prefix);
11439     SendToICS("unexamine\n");
11440 }
11441
11442 void
11443 ForwardInner(target)
11444      int target;
11445 {
11446     int limit;
11447
11448     if (appData.debugMode)
11449         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11450                 target, currentMove, forwardMostMove);
11451
11452     if (gameMode == EditPosition)
11453       return;
11454
11455     if (gameMode == PlayFromGameFile && !pausing)
11456       PauseEvent();
11457     
11458     if (gameMode == IcsExamining && pausing)
11459       limit = pauseExamForwardMostMove;
11460     else
11461       limit = forwardMostMove;
11462     
11463     if (target > limit) target = limit;
11464
11465     if (target > 0 && moveList[target - 1][0]) {
11466         int fromX, fromY, toX, toY;
11467         toX = moveList[target - 1][2] - AAA;
11468         toY = moveList[target - 1][3] - ONE;
11469         if (moveList[target - 1][1] == '@') {
11470             if (appData.highlightLastMove) {
11471                 SetHighlights(-1, -1, toX, toY);
11472             }
11473         } else {
11474             fromX = moveList[target - 1][0] - AAA;
11475             fromY = moveList[target - 1][1] - ONE;
11476             if (target == currentMove + 1) {
11477                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11478             }
11479             if (appData.highlightLastMove) {
11480                 SetHighlights(fromX, fromY, toX, toY);
11481             }
11482         }
11483     }
11484     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11485         gameMode == Training || gameMode == PlayFromGameFile || 
11486         gameMode == AnalyzeFile) {
11487         while (currentMove < target) {
11488             SendMoveToProgram(currentMove++, &first);
11489         }
11490     } else {
11491         currentMove = target;
11492     }
11493     
11494     if (gameMode == EditGame || gameMode == EndOfGame) {
11495         whiteTimeRemaining = timeRemaining[0][currentMove];
11496         blackTimeRemaining = timeRemaining[1][currentMove];
11497     }
11498     DisplayBothClocks();
11499     DisplayMove(currentMove - 1);
11500     DrawPosition(FALSE, boards[currentMove]);
11501     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11502     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11503         DisplayComment(currentMove - 1, commentList[currentMove]);
11504     }
11505 }
11506
11507
11508 void
11509 ForwardEvent()
11510 {
11511     if (gameMode == IcsExamining && !pausing) {
11512         SendToICS(ics_prefix);
11513         SendToICS("forward\n");
11514     } else {
11515         ForwardInner(currentMove + 1);
11516     }
11517 }
11518
11519 void
11520 ToEndEvent()
11521 {
11522     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11523         /* to optimze, we temporarily turn off analysis mode while we feed
11524          * the remaining moves to the engine. Otherwise we get analysis output
11525          * after each move.
11526          */ 
11527         if (first.analysisSupport) {
11528           SendToProgram("exit\nforce\n", &first);
11529           first.analyzing = FALSE;
11530         }
11531     }
11532         
11533     if (gameMode == IcsExamining && !pausing) {
11534         SendToICS(ics_prefix);
11535         SendToICS("forward 999999\n");
11536     } else {
11537         ForwardInner(forwardMostMove);
11538     }
11539
11540     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11541         /* we have fed all the moves, so reactivate analysis mode */
11542         SendToProgram("analyze\n", &first);
11543         first.analyzing = TRUE;
11544         /*first.maybeThinking = TRUE;*/
11545         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11546     }
11547 }
11548
11549 void
11550 BackwardInner(target)
11551      int target;
11552 {
11553     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11554
11555     if (appData.debugMode)
11556         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11557                 target, currentMove, forwardMostMove);
11558
11559     if (gameMode == EditPosition) return;
11560     if (currentMove <= backwardMostMove) {
11561         ClearHighlights();
11562         DrawPosition(full_redraw, boards[currentMove]);
11563         return;
11564     }
11565     if (gameMode == PlayFromGameFile && !pausing)
11566       PauseEvent();
11567     
11568     if (moveList[target][0]) {
11569         int fromX, fromY, toX, toY;
11570         toX = moveList[target][2] - AAA;
11571         toY = moveList[target][3] - ONE;
11572         if (moveList[target][1] == '@') {
11573             if (appData.highlightLastMove) {
11574                 SetHighlights(-1, -1, toX, toY);
11575             }
11576         } else {
11577             fromX = moveList[target][0] - AAA;
11578             fromY = moveList[target][1] - ONE;
11579             if (target == currentMove - 1) {
11580                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11581             }
11582             if (appData.highlightLastMove) {
11583                 SetHighlights(fromX, fromY, toX, toY);
11584             }
11585         }
11586     }
11587     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11588         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11589         while (currentMove > target) {
11590             SendToProgram("undo\n", &first);
11591             currentMove--;
11592         }
11593     } else {
11594         currentMove = target;
11595     }
11596     
11597     if (gameMode == EditGame || gameMode == EndOfGame) {
11598         whiteTimeRemaining = timeRemaining[0][currentMove];
11599         blackTimeRemaining = timeRemaining[1][currentMove];
11600     }
11601     DisplayBothClocks();
11602     DisplayMove(currentMove - 1);
11603     DrawPosition(full_redraw, boards[currentMove]);
11604     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11605     // [HGM] PV info: routine tests if comment empty
11606     DisplayComment(currentMove - 1, commentList[currentMove]);
11607 }
11608
11609 void
11610 BackwardEvent()
11611 {
11612     if (gameMode == IcsExamining && !pausing) {
11613         SendToICS(ics_prefix);
11614         SendToICS("backward\n");
11615     } else {
11616         BackwardInner(currentMove - 1);
11617     }
11618 }
11619
11620 void
11621 ToStartEvent()
11622 {
11623     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11624         /* to optimze, we temporarily turn off analysis mode while we undo
11625          * all the moves. Otherwise we get analysis output after each undo.
11626          */ 
11627         if (first.analysisSupport) {
11628           SendToProgram("exit\nforce\n", &first);
11629           first.analyzing = FALSE;
11630         }
11631     }
11632
11633     if (gameMode == IcsExamining && !pausing) {
11634         SendToICS(ics_prefix);
11635         SendToICS("backward 999999\n");
11636     } else {
11637         BackwardInner(backwardMostMove);
11638     }
11639
11640     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11641         /* we have fed all the moves, so reactivate analysis mode */
11642         SendToProgram("analyze\n", &first);
11643         first.analyzing = TRUE;
11644         /*first.maybeThinking = TRUE;*/
11645         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11646     }
11647 }
11648
11649 void
11650 ToNrEvent(int to)
11651 {
11652   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11653   if (to >= forwardMostMove) to = forwardMostMove;
11654   if (to <= backwardMostMove) to = backwardMostMove;
11655   if (to < currentMove) {
11656     BackwardInner(to);
11657   } else {
11658     ForwardInner(to);
11659   }
11660 }
11661
11662 void
11663 RevertEvent()
11664 {
11665     if (gameMode != IcsExamining) {
11666         DisplayError(_("You are not examining a game"), 0);
11667         return;
11668     }
11669     if (pausing) {
11670         DisplayError(_("You can't revert while pausing"), 0);
11671         return;
11672     }
11673     SendToICS(ics_prefix);
11674     SendToICS("revert\n");
11675 }
11676
11677 void
11678 RetractMoveEvent()
11679 {
11680     switch (gameMode) {
11681       case MachinePlaysWhite:
11682       case MachinePlaysBlack:
11683         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11684             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11685             return;
11686         }
11687         if (forwardMostMove < 2) return;
11688         currentMove = forwardMostMove = forwardMostMove - 2;
11689         whiteTimeRemaining = timeRemaining[0][currentMove];
11690         blackTimeRemaining = timeRemaining[1][currentMove];
11691         DisplayBothClocks();
11692         DisplayMove(currentMove - 1);
11693         ClearHighlights();/*!! could figure this out*/
11694         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11695         SendToProgram("remove\n", &first);
11696         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11697         break;
11698
11699       case BeginningOfGame:
11700       default:
11701         break;
11702
11703       case IcsPlayingWhite:
11704       case IcsPlayingBlack:
11705         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11706             SendToICS(ics_prefix);
11707             SendToICS("takeback 2\n");
11708         } else {
11709             SendToICS(ics_prefix);
11710             SendToICS("takeback 1\n");
11711         }
11712         break;
11713     }
11714 }
11715
11716 void
11717 MoveNowEvent()
11718 {
11719     ChessProgramState *cps;
11720
11721     switch (gameMode) {
11722       case MachinePlaysWhite:
11723         if (!WhiteOnMove(forwardMostMove)) {
11724             DisplayError(_("It is your turn"), 0);
11725             return;
11726         }
11727         cps = &first;
11728         break;
11729       case MachinePlaysBlack:
11730         if (WhiteOnMove(forwardMostMove)) {
11731             DisplayError(_("It is your turn"), 0);
11732             return;
11733         }
11734         cps = &first;
11735         break;
11736       case TwoMachinesPlay:
11737         if (WhiteOnMove(forwardMostMove) ==
11738             (first.twoMachinesColor[0] == 'w')) {
11739             cps = &first;
11740         } else {
11741             cps = &second;
11742         }
11743         break;
11744       case BeginningOfGame:
11745       default:
11746         return;
11747     }
11748     SendToProgram("?\n", cps);
11749 }
11750
11751 void
11752 TruncateGameEvent()
11753 {
11754     EditGameEvent();
11755     if (gameMode != EditGame) return;
11756     TruncateGame();
11757 }
11758
11759 void
11760 TruncateGame()
11761 {
11762     if (forwardMostMove > currentMove) {
11763         if (gameInfo.resultDetails != NULL) {
11764             free(gameInfo.resultDetails);
11765             gameInfo.resultDetails = NULL;
11766             gameInfo.result = GameUnfinished;
11767         }
11768         forwardMostMove = currentMove;
11769         HistorySet(parseList, backwardMostMove, forwardMostMove,
11770                    currentMove-1);
11771     }
11772 }
11773
11774 void
11775 HintEvent()
11776 {
11777     if (appData.noChessProgram) return;
11778     switch (gameMode) {
11779       case MachinePlaysWhite:
11780         if (WhiteOnMove(forwardMostMove)) {
11781             DisplayError(_("Wait until your turn"), 0);
11782             return;
11783         }
11784         break;
11785       case BeginningOfGame:
11786       case MachinePlaysBlack:
11787         if (!WhiteOnMove(forwardMostMove)) {
11788             DisplayError(_("Wait until your turn"), 0);
11789             return;
11790         }
11791         break;
11792       default:
11793         DisplayError(_("No hint available"), 0);
11794         return;
11795     }
11796     SendToProgram("hint\n", &first);
11797     hintRequested = TRUE;
11798 }
11799
11800 void
11801 BookEvent()
11802 {
11803     if (appData.noChessProgram) return;
11804     switch (gameMode) {
11805       case MachinePlaysWhite:
11806         if (WhiteOnMove(forwardMostMove)) {
11807             DisplayError(_("Wait until your turn"), 0);
11808             return;
11809         }
11810         break;
11811       case BeginningOfGame:
11812       case MachinePlaysBlack:
11813         if (!WhiteOnMove(forwardMostMove)) {
11814             DisplayError(_("Wait until your turn"), 0);
11815             return;
11816         }
11817         break;
11818       case EditPosition:
11819         EditPositionDone();
11820         break;
11821       case TwoMachinesPlay:
11822         return;
11823       default:
11824         break;
11825     }
11826     SendToProgram("bk\n", &first);
11827     bookOutput[0] = NULLCHAR;
11828     bookRequested = TRUE;
11829 }
11830
11831 void
11832 AboutGameEvent()
11833 {
11834     char *tags = PGNTags(&gameInfo);
11835     TagsPopUp(tags, CmailMsg());
11836     free(tags);
11837 }
11838
11839 /* end button procedures */
11840
11841 void
11842 PrintPosition(fp, move)
11843      FILE *fp;
11844      int move;
11845 {
11846     int i, j;
11847     
11848     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11849         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11850             char c = PieceToChar(boards[move][i][j]);
11851             fputc(c == 'x' ? '.' : c, fp);
11852             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11853         }
11854     }
11855     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11856       fprintf(fp, "white to play\n");
11857     else
11858       fprintf(fp, "black to play\n");
11859 }
11860
11861 void
11862 PrintOpponents(fp)
11863      FILE *fp;
11864 {
11865     if (gameInfo.white != NULL) {
11866         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11867     } else {
11868         fprintf(fp, "\n");
11869     }
11870 }
11871
11872 /* Find last component of program's own name, using some heuristics */
11873 void
11874 TidyProgramName(prog, host, buf)
11875      char *prog, *host, buf[MSG_SIZ];
11876 {
11877     char *p, *q;
11878     int local = (strcmp(host, "localhost") == 0);
11879     while (!local && (p = strchr(prog, ';')) != NULL) {
11880         p++;
11881         while (*p == ' ') p++;
11882         prog = p;
11883     }
11884     if (*prog == '"' || *prog == '\'') {
11885         q = strchr(prog + 1, *prog);
11886     } else {
11887         q = strchr(prog, ' ');
11888     }
11889     if (q == NULL) q = prog + strlen(prog);
11890     p = q;
11891     while (p >= prog && *p != '/' && *p != '\\') p--;
11892     p++;
11893     if(p == prog && *p == '"') p++;
11894     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11895     memcpy(buf, p, q - p);
11896     buf[q - p] = NULLCHAR;
11897     if (!local) {
11898         strcat(buf, "@");
11899         strcat(buf, host);
11900     }
11901 }
11902
11903 char *
11904 TimeControlTagValue()
11905 {
11906     char buf[MSG_SIZ];
11907     if (!appData.clockMode) {
11908         strcpy(buf, "-");
11909     } else if (movesPerSession > 0) {
11910         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11911     } else if (timeIncrement == 0) {
11912         sprintf(buf, "%ld", timeControl/1000);
11913     } else {
11914         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11915     }
11916     return StrSave(buf);
11917 }
11918
11919 void
11920 SetGameInfo()
11921 {
11922     /* This routine is used only for certain modes */
11923     VariantClass v = gameInfo.variant;
11924     ClearGameInfo(&gameInfo);
11925     gameInfo.variant = v;
11926
11927     switch (gameMode) {
11928       case MachinePlaysWhite:
11929         gameInfo.event = StrSave( appData.pgnEventHeader );
11930         gameInfo.site = StrSave(HostName());
11931         gameInfo.date = PGNDate();
11932         gameInfo.round = StrSave("-");
11933         gameInfo.white = StrSave(first.tidy);
11934         gameInfo.black = StrSave(UserName());
11935         gameInfo.timeControl = TimeControlTagValue();
11936         break;
11937
11938       case MachinePlaysBlack:
11939         gameInfo.event = StrSave( appData.pgnEventHeader );
11940         gameInfo.site = StrSave(HostName());
11941         gameInfo.date = PGNDate();
11942         gameInfo.round = StrSave("-");
11943         gameInfo.white = StrSave(UserName());
11944         gameInfo.black = StrSave(first.tidy);
11945         gameInfo.timeControl = TimeControlTagValue();
11946         break;
11947
11948       case TwoMachinesPlay:
11949         gameInfo.event = StrSave( appData.pgnEventHeader );
11950         gameInfo.site = StrSave(HostName());
11951         gameInfo.date = PGNDate();
11952         if (matchGame > 0) {
11953             char buf[MSG_SIZ];
11954             sprintf(buf, "%d", matchGame);
11955             gameInfo.round = StrSave(buf);
11956         } else {
11957             gameInfo.round = StrSave("-");
11958         }
11959         if (first.twoMachinesColor[0] == 'w') {
11960             gameInfo.white = StrSave(first.tidy);
11961             gameInfo.black = StrSave(second.tidy);
11962         } else {
11963             gameInfo.white = StrSave(second.tidy);
11964             gameInfo.black = StrSave(first.tidy);
11965         }
11966         gameInfo.timeControl = TimeControlTagValue();
11967         break;
11968
11969       case EditGame:
11970         gameInfo.event = StrSave("Edited game");
11971         gameInfo.site = StrSave(HostName());
11972         gameInfo.date = PGNDate();
11973         gameInfo.round = StrSave("-");
11974         gameInfo.white = StrSave("-");
11975         gameInfo.black = StrSave("-");
11976         break;
11977
11978       case EditPosition:
11979         gameInfo.event = StrSave("Edited position");
11980         gameInfo.site = StrSave(HostName());
11981         gameInfo.date = PGNDate();
11982         gameInfo.round = StrSave("-");
11983         gameInfo.white = StrSave("-");
11984         gameInfo.black = StrSave("-");
11985         break;
11986
11987       case IcsPlayingWhite:
11988       case IcsPlayingBlack:
11989       case IcsObserving:
11990       case IcsExamining:
11991         break;
11992
11993       case PlayFromGameFile:
11994         gameInfo.event = StrSave("Game from non-PGN file");
11995         gameInfo.site = StrSave(HostName());
11996         gameInfo.date = PGNDate();
11997         gameInfo.round = StrSave("-");
11998         gameInfo.white = StrSave("?");
11999         gameInfo.black = StrSave("?");
12000         break;
12001
12002       default:
12003         break;
12004     }
12005 }
12006
12007 void
12008 ReplaceComment(index, text)
12009      int index;
12010      char *text;
12011 {
12012     int len;
12013
12014     while (*text == '\n') text++;
12015     len = strlen(text);
12016     while (len > 0 && text[len - 1] == '\n') len--;
12017
12018     if (commentList[index] != NULL)
12019       free(commentList[index]);
12020
12021     if (len == 0) {
12022         commentList[index] = NULL;
12023         return;
12024     }
12025     commentList[index] = (char *) malloc(len + 2);
12026     strncpy(commentList[index], text, len);
12027     commentList[index][len] = '\n';
12028     commentList[index][len + 1] = NULLCHAR;
12029 }
12030
12031 void
12032 CrushCRs(text)
12033      char *text;
12034 {
12035   char *p = text;
12036   char *q = text;
12037   char ch;
12038
12039   do {
12040     ch = *p++;
12041     if (ch == '\r') continue;
12042     *q++ = ch;
12043   } while (ch != '\0');
12044 }
12045
12046 void
12047 AppendComment(index, text)
12048      int index;
12049      char *text;
12050 {
12051     int oldlen, len;
12052     char *old;
12053
12054     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12055
12056     CrushCRs(text);
12057     while (*text == '\n') text++;
12058     len = strlen(text);
12059     while (len > 0 && text[len - 1] == '\n') len--;
12060
12061     if (len == 0) return;
12062
12063     if (commentList[index] != NULL) {
12064         old = commentList[index];
12065         oldlen = strlen(old);
12066         commentList[index] = (char *) malloc(oldlen + len + 2);
12067         strcpy(commentList[index], old);
12068         free(old);
12069         strncpy(&commentList[index][oldlen], text, len);
12070         commentList[index][oldlen + len] = '\n';
12071         commentList[index][oldlen + len + 1] = NULLCHAR;
12072     } else {
12073         commentList[index] = (char *) malloc(len + 2);
12074         strncpy(commentList[index], text, len);
12075         commentList[index][len] = '\n';
12076         commentList[index][len + 1] = NULLCHAR;
12077     }
12078 }
12079
12080 static char * FindStr( char * text, char * sub_text )
12081 {
12082     char * result = strstr( text, sub_text );
12083
12084     if( result != NULL ) {
12085         result += strlen( sub_text );
12086     }
12087
12088     return result;
12089 }
12090
12091 /* [AS] Try to extract PV info from PGN comment */
12092 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12093 char *GetInfoFromComment( int index, char * text )
12094 {
12095     char * sep = text;
12096
12097     if( text != NULL && index > 0 ) {
12098         int score = 0;
12099         int depth = 0;
12100         int time = -1, sec = 0, deci;
12101         char * s_eval = FindStr( text, "[%eval " );
12102         char * s_emt = FindStr( text, "[%emt " );
12103
12104         if( s_eval != NULL || s_emt != NULL ) {
12105             /* New style */
12106             char delim;
12107
12108             if( s_eval != NULL ) {
12109                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12110                     return text;
12111                 }
12112
12113                 if( delim != ']' ) {
12114                     return text;
12115                 }
12116             }
12117
12118             if( s_emt != NULL ) {
12119             }
12120         }
12121         else {
12122             /* We expect something like: [+|-]nnn.nn/dd */
12123             int score_lo = 0;
12124
12125             sep = strchr( text, '/' );
12126             if( sep == NULL || sep < (text+4) ) {
12127                 return text;
12128             }
12129
12130             time = -1; sec = -1; deci = -1;
12131             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12132                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12133                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12134                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12135                 return text;
12136             }
12137
12138             if( score_lo < 0 || score_lo >= 100 ) {
12139                 return text;
12140             }
12141
12142             if(sec >= 0) time = 600*time + 10*sec; else
12143             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12144
12145             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12146
12147             /* [HGM] PV time: now locate end of PV info */
12148             while( *++sep >= '0' && *sep <= '9'); // strip depth
12149             if(time >= 0)
12150             while( *++sep >= '0' && *sep <= '9'); // strip time
12151             if(sec >= 0)
12152             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12153             if(deci >= 0)
12154             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12155             while(*sep == ' ') sep++;
12156         }
12157
12158         if( depth <= 0 ) {
12159             return text;
12160         }
12161
12162         if( time < 0 ) {
12163             time = -1;
12164         }
12165
12166         pvInfoList[index-1].depth = depth;
12167         pvInfoList[index-1].score = score;
12168         pvInfoList[index-1].time  = 10*time; // centi-sec
12169     }
12170     return sep;
12171 }
12172
12173 void
12174 SendToProgram(message, cps)
12175      char *message;
12176      ChessProgramState *cps;
12177 {
12178     int count, outCount, error;
12179     char buf[MSG_SIZ];
12180
12181     if (cps->pr == NULL) return;
12182     Attention(cps);
12183     
12184     if (appData.debugMode) {
12185         TimeMark now;
12186         GetTimeMark(&now);
12187         fprintf(debugFP, "%ld >%-6s: %s", 
12188                 SubtractTimeMarks(&now, &programStartTime),
12189                 cps->which, message);
12190     }
12191     
12192     count = strlen(message);
12193     outCount = OutputToProcess(cps->pr, message, count, &error);
12194     if (outCount < count && !exiting 
12195                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12196         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12197         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12198             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12199                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12200                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12201             } else {
12202                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12203             }
12204             gameInfo.resultDetails = buf;
12205         }
12206         DisplayFatalError(buf, error, 1);
12207     }
12208 }
12209
12210 void
12211 ReceiveFromProgram(isr, closure, message, count, error)
12212      InputSourceRef isr;
12213      VOIDSTAR closure;
12214      char *message;
12215      int count;
12216      int error;
12217 {
12218     char *end_str;
12219     char buf[MSG_SIZ];
12220     ChessProgramState *cps = (ChessProgramState *)closure;
12221
12222     if (isr != cps->isr) return; /* Killed intentionally */
12223     if (count <= 0) {
12224         if (count == 0) {
12225             sprintf(buf,
12226                     _("Error: %s chess program (%s) exited unexpectedly"),
12227                     cps->which, cps->program);
12228         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12229                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12230                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12231                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12232                 } else {
12233                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12234                 }
12235                 gameInfo.resultDetails = buf;
12236             }
12237             RemoveInputSource(cps->isr);
12238             DisplayFatalError(buf, 0, 1);
12239         } else {
12240             sprintf(buf,
12241                     _("Error reading from %s chess program (%s)"),
12242                     cps->which, cps->program);
12243             RemoveInputSource(cps->isr);
12244
12245             /* [AS] Program is misbehaving badly... kill it */
12246             if( count == -2 ) {
12247                 DestroyChildProcess( cps->pr, 9 );
12248                 cps->pr = NoProc;
12249             }
12250
12251             DisplayFatalError(buf, error, 1);
12252         }
12253         return;
12254     }
12255     
12256     if ((end_str = strchr(message, '\r')) != NULL)
12257       *end_str = NULLCHAR;
12258     if ((end_str = strchr(message, '\n')) != NULL)
12259       *end_str = NULLCHAR;
12260     
12261     if (appData.debugMode) {
12262         TimeMark now; int print = 1;
12263         char *quote = ""; char c; int i;
12264
12265         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12266                 char start = message[0];
12267                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12268                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12269                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12270                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12271                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12272                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12273                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
12274                         { quote = "# "; print = (appData.engineComments == 2); }
12275                 message[0] = start; // restore original message
12276         }
12277         if(print) {
12278                 GetTimeMark(&now);
12279                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12280                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12281                         quote,
12282                         message);
12283         }
12284     }
12285
12286     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12287     if (appData.icsEngineAnalyze) {
12288         if (strstr(message, "whisper") != NULL ||
12289              strstr(message, "kibitz") != NULL || 
12290             strstr(message, "tellics") != NULL) return;
12291     }
12292
12293     HandleMachineMove(message, cps);
12294 }
12295
12296
12297 void
12298 SendTimeControl(cps, mps, tc, inc, sd, st)
12299      ChessProgramState *cps;
12300      int mps, inc, sd, st;
12301      long tc;
12302 {
12303     char buf[MSG_SIZ];
12304     int seconds;
12305
12306     if( timeControl_2 > 0 ) {
12307         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12308             tc = timeControl_2;
12309         }
12310     }
12311     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12312     inc /= cps->timeOdds;
12313     st  /= cps->timeOdds;
12314
12315     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12316
12317     if (st > 0) {
12318       /* Set exact time per move, normally using st command */
12319       if (cps->stKludge) {
12320         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12321         seconds = st % 60;
12322         if (seconds == 0) {
12323           sprintf(buf, "level 1 %d\n", st/60);
12324         } else {
12325           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12326         }
12327       } else {
12328         sprintf(buf, "st %d\n", st);
12329       }
12330     } else {
12331       /* Set conventional or incremental time control, using level command */
12332       if (seconds == 0) {
12333         /* Note old gnuchess bug -- minutes:seconds used to not work.
12334            Fixed in later versions, but still avoid :seconds
12335            when seconds is 0. */
12336         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12337       } else {
12338         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12339                 seconds, inc/1000);
12340       }
12341     }
12342     SendToProgram(buf, cps);
12343
12344     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12345     /* Orthogonally, limit search to given depth */
12346     if (sd > 0) {
12347       if (cps->sdKludge) {
12348         sprintf(buf, "depth\n%d\n", sd);
12349       } else {
12350         sprintf(buf, "sd %d\n", sd);
12351       }
12352       SendToProgram(buf, cps);
12353     }
12354
12355     if(cps->nps > 0) { /* [HGM] nps */
12356         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12357         else {
12358                 sprintf(buf, "nps %d\n", cps->nps);
12359               SendToProgram(buf, cps);
12360         }
12361     }
12362 }
12363
12364 ChessProgramState *WhitePlayer()
12365 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12366 {
12367     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12368        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12369         return &second;
12370     return &first;
12371 }
12372
12373 void
12374 SendTimeRemaining(cps, machineWhite)
12375      ChessProgramState *cps;
12376      int /*boolean*/ machineWhite;
12377 {
12378     char message[MSG_SIZ];
12379     long time, otime;
12380
12381     /* Note: this routine must be called when the clocks are stopped
12382        or when they have *just* been set or switched; otherwise
12383        it will be off by the time since the current tick started.
12384     */
12385     if (machineWhite) {
12386         time = whiteTimeRemaining / 10;
12387         otime = blackTimeRemaining / 10;
12388     } else {
12389         time = blackTimeRemaining / 10;
12390         otime = whiteTimeRemaining / 10;
12391     }
12392     /* [HGM] translate opponent's time by time-odds factor */
12393     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12394     if (appData.debugMode) {
12395         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12396     }
12397
12398     if (time <= 0) time = 1;
12399     if (otime <= 0) otime = 1;
12400     
12401     sprintf(message, "time %ld\n", time);
12402     SendToProgram(message, cps);
12403
12404     sprintf(message, "otim %ld\n", otime);
12405     SendToProgram(message, cps);
12406 }
12407
12408 int
12409 BoolFeature(p, name, loc, cps)
12410      char **p;
12411      char *name;
12412      int *loc;
12413      ChessProgramState *cps;
12414 {
12415   char buf[MSG_SIZ];
12416   int len = strlen(name);
12417   int val;
12418   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12419     (*p) += len + 1;
12420     sscanf(*p, "%d", &val);
12421     *loc = (val != 0);
12422     while (**p && **p != ' ') (*p)++;
12423     sprintf(buf, "accepted %s\n", name);
12424     SendToProgram(buf, cps);
12425     return TRUE;
12426   }
12427   return FALSE;
12428 }
12429
12430 int
12431 IntFeature(p, name, loc, cps)
12432      char **p;
12433      char *name;
12434      int *loc;
12435      ChessProgramState *cps;
12436 {
12437   char buf[MSG_SIZ];
12438   int len = strlen(name);
12439   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12440     (*p) += len + 1;
12441     sscanf(*p, "%d", loc);
12442     while (**p && **p != ' ') (*p)++;
12443     sprintf(buf, "accepted %s\n", name);
12444     SendToProgram(buf, cps);
12445     return TRUE;
12446   }
12447   return FALSE;
12448 }
12449
12450 int
12451 StringFeature(p, name, loc, cps)
12452      char **p;
12453      char *name;
12454      char loc[];
12455      ChessProgramState *cps;
12456 {
12457   char buf[MSG_SIZ];
12458   int len = strlen(name);
12459   if (strncmp((*p), name, len) == 0
12460       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12461     (*p) += len + 2;
12462     sscanf(*p, "%[^\"]", loc);
12463     while (**p && **p != '\"') (*p)++;
12464     if (**p == '\"') (*p)++;
12465     sprintf(buf, "accepted %s\n", name);
12466     SendToProgram(buf, cps);
12467     return TRUE;
12468   }
12469   return FALSE;
12470 }
12471
12472 int 
12473 ParseOption(Option *opt, ChessProgramState *cps)
12474 // [HGM] options: process the string that defines an engine option, and determine
12475 // name, type, default value, and allowed value range
12476 {
12477         char *p, *q, buf[MSG_SIZ];
12478         int n, min = (-1)<<31, max = 1<<31, def;
12479
12480         if(p = strstr(opt->name, " -spin ")) {
12481             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12482             if(max < min) max = min; // enforce consistency
12483             if(def < min) def = min;
12484             if(def > max) def = max;
12485             opt->value = def;
12486             opt->min = min;
12487             opt->max = max;
12488             opt->type = Spin;
12489         } else if((p = strstr(opt->name, " -slider "))) {
12490             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12491             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12492             if(max < min) max = min; // enforce consistency
12493             if(def < min) def = min;
12494             if(def > max) def = max;
12495             opt->value = def;
12496             opt->min = min;
12497             opt->max = max;
12498             opt->type = Spin; // Slider;
12499         } else if((p = strstr(opt->name, " -string "))) {
12500             opt->textValue = p+9;
12501             opt->type = TextBox;
12502         } else if((p = strstr(opt->name, " -file "))) {
12503             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12504             opt->textValue = p+7;
12505             opt->type = TextBox; // FileName;
12506         } else if((p = strstr(opt->name, " -path "))) {
12507             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12508             opt->textValue = p+7;
12509             opt->type = TextBox; // PathName;
12510         } else if(p = strstr(opt->name, " -check ")) {
12511             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12512             opt->value = (def != 0);
12513             opt->type = CheckBox;
12514         } else if(p = strstr(opt->name, " -combo ")) {
12515             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12516             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12517             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12518             opt->value = n = 0;
12519             while(q = StrStr(q, " /// ")) {
12520                 n++; *q = 0;    // count choices, and null-terminate each of them
12521                 q += 5;
12522                 if(*q == '*') { // remember default, which is marked with * prefix
12523                     q++;
12524                     opt->value = n;
12525                 }
12526                 cps->comboList[cps->comboCnt++] = q;
12527             }
12528             cps->comboList[cps->comboCnt++] = NULL;
12529             opt->max = n + 1;
12530             opt->type = ComboBox;
12531         } else if(p = strstr(opt->name, " -button")) {
12532             opt->type = Button;
12533         } else if(p = strstr(opt->name, " -save")) {
12534             opt->type = SaveButton;
12535         } else return FALSE;
12536         *p = 0; // terminate option name
12537         // now look if the command-line options define a setting for this engine option.
12538         if(cps->optionSettings && cps->optionSettings[0])
12539             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12540         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12541                 sprintf(buf, "option %s", p);
12542                 if(p = strstr(buf, ",")) *p = 0;
12543                 strcat(buf, "\n");
12544                 SendToProgram(buf, cps);
12545         }
12546         return TRUE;
12547 }
12548
12549 void
12550 FeatureDone(cps, val)
12551      ChessProgramState* cps;
12552      int val;
12553 {
12554   DelayedEventCallback cb = GetDelayedEvent();
12555   if ((cb == InitBackEnd3 && cps == &first) ||
12556       (cb == TwoMachinesEventIfReady && cps == &second)) {
12557     CancelDelayedEvent();
12558     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12559   }
12560   cps->initDone = val;
12561 }
12562
12563 /* Parse feature command from engine */
12564 void
12565 ParseFeatures(args, cps)
12566      char* args;
12567      ChessProgramState *cps;  
12568 {
12569   char *p = args;
12570   char *q;
12571   int val;
12572   char buf[MSG_SIZ];
12573
12574   for (;;) {
12575     while (*p == ' ') p++;
12576     if (*p == NULLCHAR) return;
12577
12578     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12579     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12580     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12581     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12582     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12583     if (BoolFeature(&p, "reuse", &val, cps)) {
12584       /* Engine can disable reuse, but can't enable it if user said no */
12585       if (!val) cps->reuse = FALSE;
12586       continue;
12587     }
12588     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12589     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12590       if (gameMode == TwoMachinesPlay) {
12591         DisplayTwoMachinesTitle();
12592       } else {
12593         DisplayTitle("");
12594       }
12595       continue;
12596     }
12597     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12598     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12599     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12600     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12601     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12602     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12603     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12604     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12605     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12606     if (IntFeature(&p, "done", &val, cps)) {
12607       FeatureDone(cps, val);
12608       continue;
12609     }
12610     /* Added by Tord: */
12611     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12612     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12613     /* End of additions by Tord */
12614
12615     /* [HGM] added features: */
12616     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12617     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12618     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12619     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12620     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12621     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12622     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12623         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12624             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12625             SendToProgram(buf, cps);
12626             continue;
12627         }
12628         if(cps->nrOptions >= MAX_OPTIONS) {
12629             cps->nrOptions--;
12630             sprintf(buf, "%s engine has too many options\n", cps->which);
12631             DisplayError(buf, 0);
12632         }
12633         continue;
12634     }
12635     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12636     /* End of additions by HGM */
12637
12638     /* unknown feature: complain and skip */
12639     q = p;
12640     while (*q && *q != '=') q++;
12641     sprintf(buf, "rejected %.*s\n", q-p, p);
12642     SendToProgram(buf, cps);
12643     p = q;
12644     if (*p == '=') {
12645       p++;
12646       if (*p == '\"') {
12647         p++;
12648         while (*p && *p != '\"') p++;
12649         if (*p == '\"') p++;
12650       } else {
12651         while (*p && *p != ' ') p++;
12652       }
12653     }
12654   }
12655
12656 }
12657
12658 void
12659 PeriodicUpdatesEvent(newState)
12660      int newState;
12661 {
12662     if (newState == appData.periodicUpdates)
12663       return;
12664
12665     appData.periodicUpdates=newState;
12666
12667     /* Display type changes, so update it now */
12668     DisplayAnalysis();
12669
12670     /* Get the ball rolling again... */
12671     if (newState) {
12672         AnalysisPeriodicEvent(1);
12673         StartAnalysisClock();
12674     }
12675 }
12676
12677 void
12678 PonderNextMoveEvent(newState)
12679      int newState;
12680 {
12681     if (newState == appData.ponderNextMove) return;
12682     if (gameMode == EditPosition) EditPositionDone();
12683     if (newState) {
12684         SendToProgram("hard\n", &first);
12685         if (gameMode == TwoMachinesPlay) {
12686             SendToProgram("hard\n", &second);
12687         }
12688     } else {
12689         SendToProgram("easy\n", &first);
12690         thinkOutput[0] = NULLCHAR;
12691         if (gameMode == TwoMachinesPlay) {
12692             SendToProgram("easy\n", &second);
12693         }
12694     }
12695     appData.ponderNextMove = newState;
12696 }
12697
12698 void
12699 NewSettingEvent(option, command, value)
12700      char *command;
12701      int option, value;
12702 {
12703     char buf[MSG_SIZ];
12704
12705     if (gameMode == EditPosition) EditPositionDone();
12706     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12707     SendToProgram(buf, &first);
12708     if (gameMode == TwoMachinesPlay) {
12709         SendToProgram(buf, &second);
12710     }
12711 }
12712
12713 void
12714 ShowThinkingEvent()
12715 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12716 {
12717     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12718     int newState = appData.showThinking
12719         // [HGM] thinking: other features now need thinking output as well
12720         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12721     
12722     if (oldState == newState) return;
12723     oldState = newState;
12724     if (gameMode == EditPosition) EditPositionDone();
12725     if (oldState) {
12726         SendToProgram("post\n", &first);
12727         if (gameMode == TwoMachinesPlay) {
12728             SendToProgram("post\n", &second);
12729         }
12730     } else {
12731         SendToProgram("nopost\n", &first);
12732         thinkOutput[0] = NULLCHAR;
12733         if (gameMode == TwoMachinesPlay) {
12734             SendToProgram("nopost\n", &second);
12735         }
12736     }
12737 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12738 }
12739
12740 void
12741 AskQuestionEvent(title, question, replyPrefix, which)
12742      char *title; char *question; char *replyPrefix; char *which;
12743 {
12744   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12745   if (pr == NoProc) return;
12746   AskQuestion(title, question, replyPrefix, pr);
12747 }
12748
12749 void
12750 DisplayMove(moveNumber)
12751      int moveNumber;
12752 {
12753     char message[MSG_SIZ];
12754     char res[MSG_SIZ];
12755     char cpThinkOutput[MSG_SIZ];
12756
12757     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12758     
12759     if (moveNumber == forwardMostMove - 1 || 
12760         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12761
12762         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12763
12764         if (strchr(cpThinkOutput, '\n')) {
12765             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12766         }
12767     } else {
12768         *cpThinkOutput = NULLCHAR;
12769     }
12770
12771     /* [AS] Hide thinking from human user */
12772     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12773         *cpThinkOutput = NULLCHAR;
12774         if( thinkOutput[0] != NULLCHAR ) {
12775             int i;
12776
12777             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12778                 cpThinkOutput[i] = '.';
12779             }
12780             cpThinkOutput[i] = NULLCHAR;
12781             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12782         }
12783     }
12784
12785     if (moveNumber == forwardMostMove - 1 &&
12786         gameInfo.resultDetails != NULL) {
12787         if (gameInfo.resultDetails[0] == NULLCHAR) {
12788             sprintf(res, " %s", PGNResult(gameInfo.result));
12789         } else {
12790             sprintf(res, " {%s} %s",
12791                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12792         }
12793     } else {
12794         res[0] = NULLCHAR;
12795     }
12796
12797     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12798         DisplayMessage(res, cpThinkOutput);
12799     } else {
12800         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12801                 WhiteOnMove(moveNumber) ? " " : ".. ",
12802                 parseList[moveNumber], res);
12803         DisplayMessage(message, cpThinkOutput);
12804     }
12805 }
12806
12807 void
12808 DisplayAnalysisText(text)
12809      char *text;
12810 {
12811     char buf[MSG_SIZ];
12812
12813     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile 
12814                || appData.icsEngineAnalyze) {
12815         sprintf(buf, "Analysis (%s)", first.tidy);
12816         AnalysisPopUp(buf, text);
12817     }
12818 }
12819
12820 static int
12821 only_one_move(str)
12822      char *str;
12823 {
12824     while (*str && isspace(*str)) ++str;
12825     while (*str && !isspace(*str)) ++str;
12826     if (!*str) return 1;
12827     while (*str && isspace(*str)) ++str;
12828     if (!*str) return 1;
12829     return 0;
12830 }
12831
12832 void
12833 DisplayAnalysis()
12834 {
12835     char buf[MSG_SIZ];
12836     char lst[MSG_SIZ / 2];
12837     double nps;
12838     static char *xtra[] = { "", " (--)", " (++)" };
12839     int h, m, s, cs;
12840   
12841     if (programStats.time == 0) {
12842         programStats.time = 1;
12843     }
12844   
12845     if (programStats.got_only_move) {
12846         safeStrCpy(buf, programStats.movelist, sizeof(buf));
12847     } else {
12848         safeStrCpy( lst, programStats.movelist, sizeof(lst));
12849
12850         nps = (u64ToDouble(programStats.nodes) /
12851              ((double)programStats.time /100.0));
12852
12853         cs = programStats.time % 100;
12854         s = programStats.time / 100;
12855         h = (s / (60*60));
12856         s = s - h*60*60;
12857         m = (s/60);
12858         s = s - m*60;
12859
12860         if (programStats.moves_left > 0 && appData.periodicUpdates) {
12861           if (programStats.move_name[0] != NULLCHAR) {
12862             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12863                     programStats.depth,
12864                     programStats.nr_moves-programStats.moves_left,
12865                     programStats.nr_moves, programStats.move_name,
12866                     ((float)programStats.score)/100.0, lst,
12867                     only_one_move(lst)?
12868                     xtra[programStats.got_fail] : "",
12869                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12870           } else {
12871             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12872                     programStats.depth,
12873                     programStats.nr_moves-programStats.moves_left,
12874                     programStats.nr_moves, ((float)programStats.score)/100.0,
12875                     lst,
12876                     only_one_move(lst)?
12877                     xtra[programStats.got_fail] : "",
12878                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12879           }
12880         } else {
12881             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12882                     programStats.depth,
12883                     ((float)programStats.score)/100.0,
12884                     lst,
12885                     only_one_move(lst)?
12886                     xtra[programStats.got_fail] : "",
12887                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12888         }
12889     }
12890     DisplayAnalysisText(buf);
12891 }
12892
12893 void
12894 DisplayComment(moveNumber, text)
12895      int moveNumber;
12896      char *text;
12897 {
12898     char title[MSG_SIZ];
12899     char buf[8000]; // comment can be long!
12900     int score, depth;
12901
12902     if( appData.autoDisplayComment ) {
12903         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12904             strcpy(title, "Comment");
12905         } else {
12906             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12907                     WhiteOnMove(moveNumber) ? " " : ".. ",
12908                     parseList[moveNumber]);
12909         }
12910         // [HGM] PV info: display PV info together with (or as) comment
12911         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12912             if(text == NULL) text = "";                                           
12913             score = pvInfoList[moveNumber].score;
12914             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12915                               depth, (pvInfoList[moveNumber].time+50)/100, text);
12916             text = buf;
12917         }
12918     } else title[0] = 0;
12919
12920     if (text != NULL)
12921         CommentPopUp(title, text);
12922 }
12923
12924 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12925  * might be busy thinking or pondering.  It can be omitted if your
12926  * gnuchess is configured to stop thinking immediately on any user
12927  * input.  However, that gnuchess feature depends on the FIONREAD
12928  * ioctl, which does not work properly on some flavors of Unix.
12929  */
12930 void
12931 Attention(cps)
12932      ChessProgramState *cps;
12933 {
12934 #if ATTENTION
12935     if (!cps->useSigint) return;
12936     if (appData.noChessProgram || (cps->pr == NoProc)) return;
12937     switch (gameMode) {
12938       case MachinePlaysWhite:
12939       case MachinePlaysBlack:
12940       case TwoMachinesPlay:
12941       case IcsPlayingWhite:
12942       case IcsPlayingBlack:
12943       case AnalyzeMode:
12944       case AnalyzeFile:
12945         /* Skip if we know it isn't thinking */
12946         if (!cps->maybeThinking) return;
12947         if (appData.debugMode)
12948           fprintf(debugFP, "Interrupting %s\n", cps->which);
12949         InterruptChildProcess(cps->pr);
12950         cps->maybeThinking = FALSE;
12951         break;
12952       default:
12953         break;
12954     }
12955 #endif /*ATTENTION*/
12956 }
12957
12958 int
12959 CheckFlags()
12960 {
12961     if (whiteTimeRemaining <= 0) {
12962         if (!whiteFlag) {
12963             whiteFlag = TRUE;
12964             if (appData.icsActive) {
12965                 if (appData.autoCallFlag &&
12966                     gameMode == IcsPlayingBlack && !blackFlag) {
12967                   SendToICS(ics_prefix);
12968                   SendToICS("flag\n");
12969                 }
12970             } else {
12971                 if (blackFlag) {
12972                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12973                 } else {
12974                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12975                     if (appData.autoCallFlag) {
12976                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12977                         return TRUE;
12978                     }
12979                 }
12980             }
12981         }
12982     }
12983     if (blackTimeRemaining <= 0) {
12984         if (!blackFlag) {
12985             blackFlag = TRUE;
12986             if (appData.icsActive) {
12987                 if (appData.autoCallFlag &&
12988                     gameMode == IcsPlayingWhite && !whiteFlag) {
12989                   SendToICS(ics_prefix);
12990                   SendToICS("flag\n");
12991                 }
12992             } else {
12993                 if (whiteFlag) {
12994                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12995                 } else {
12996                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12997                     if (appData.autoCallFlag) {
12998                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12999                         return TRUE;
13000                     }
13001                 }
13002             }
13003         }
13004     }
13005     return FALSE;
13006 }
13007
13008 void
13009 CheckTimeControl()
13010 {
13011     if (!appData.clockMode || appData.icsActive ||
13012         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13013
13014     /*
13015      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13016      */
13017     if ( !WhiteOnMove(forwardMostMove) )
13018         /* White made time control */
13019         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13020         /* [HGM] time odds: correct new time quota for time odds! */
13021                                             / WhitePlayer()->timeOdds;
13022       else
13023         /* Black made time control */
13024         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13025                                             / WhitePlayer()->other->timeOdds;
13026 }
13027
13028 void
13029 DisplayBothClocks()
13030 {
13031     int wom = gameMode == EditPosition ?
13032       !blackPlaysFirst : WhiteOnMove(currentMove);
13033     DisplayWhiteClock(whiteTimeRemaining, wom);
13034     DisplayBlackClock(blackTimeRemaining, !wom);
13035 }
13036
13037
13038 /* Timekeeping seems to be a portability nightmare.  I think everyone
13039    has ftime(), but I'm really not sure, so I'm including some ifdefs
13040    to use other calls if you don't.  Clocks will be less accurate if
13041    you have neither ftime nor gettimeofday.
13042 */
13043
13044 /* VS 2008 requires the #include outside of the function */
13045 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13046 #include <sys/timeb.h>
13047 #endif
13048
13049 /* Get the current time as a TimeMark */
13050 void
13051 GetTimeMark(tm)
13052      TimeMark *tm;
13053 {
13054 #if HAVE_GETTIMEOFDAY
13055
13056     struct timeval timeVal;
13057     struct timezone timeZone;
13058
13059     gettimeofday(&timeVal, &timeZone);
13060     tm->sec = (long) timeVal.tv_sec; 
13061     tm->ms = (int) (timeVal.tv_usec / 1000L);
13062
13063 #else /*!HAVE_GETTIMEOFDAY*/
13064 #if HAVE_FTIME
13065
13066 // include <sys/timeb.h> / moved to just above start of function
13067     struct timeb timeB;
13068
13069     ftime(&timeB);
13070     tm->sec = (long) timeB.time;
13071     tm->ms = (int) timeB.millitm;
13072
13073 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13074     tm->sec = (long) time(NULL);
13075     tm->ms = 0;
13076 #endif
13077 #endif
13078 }
13079
13080 /* Return the difference in milliseconds between two
13081    time marks.  We assume the difference will fit in a long!
13082 */
13083 long
13084 SubtractTimeMarks(tm2, tm1)
13085      TimeMark *tm2, *tm1;
13086 {
13087     return 1000L*(tm2->sec - tm1->sec) +
13088            (long) (tm2->ms - tm1->ms);
13089 }
13090
13091
13092 /*
13093  * Code to manage the game clocks.
13094  *
13095  * In tournament play, black starts the clock and then white makes a move.
13096  * We give the human user a slight advantage if he is playing white---the
13097  * clocks don't run until he makes his first move, so it takes zero time.
13098  * Also, we don't account for network lag, so we could get out of sync
13099  * with GNU Chess's clock -- but then, referees are always right.  
13100  */
13101
13102 static TimeMark tickStartTM;
13103 static long intendedTickLength;
13104
13105 long
13106 NextTickLength(timeRemaining)
13107      long timeRemaining;
13108 {
13109     long nominalTickLength, nextTickLength;
13110
13111     if (timeRemaining > 0L && timeRemaining <= 10000L)
13112       nominalTickLength = 100L;
13113     else
13114       nominalTickLength = 1000L;
13115     nextTickLength = timeRemaining % nominalTickLength;
13116     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13117
13118     return nextTickLength;
13119 }
13120
13121 /* Adjust clock one minute up or down */
13122 void
13123 AdjustClock(Boolean which, int dir)
13124 {
13125     if(which) blackTimeRemaining += 60000*dir;
13126     else      whiteTimeRemaining += 60000*dir;
13127     DisplayBothClocks();
13128 }
13129
13130 /* Stop clocks and reset to a fresh time control */
13131 void
13132 ResetClocks() 
13133 {
13134     (void) StopClockTimer();
13135     if (appData.icsActive) {
13136         whiteTimeRemaining = blackTimeRemaining = 0;
13137     } else { /* [HGM] correct new time quote for time odds */
13138         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13139         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13140     }
13141     if (whiteFlag || blackFlag) {
13142         DisplayTitle("");
13143         whiteFlag = blackFlag = FALSE;
13144     }
13145     DisplayBothClocks();
13146 }
13147
13148 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13149
13150 /* Decrement running clock by amount of time that has passed */
13151 void
13152 DecrementClocks()
13153 {
13154     long timeRemaining;
13155     long lastTickLength, fudge;
13156     TimeMark now;
13157
13158     if (!appData.clockMode) return;
13159     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13160         
13161     GetTimeMark(&now);
13162
13163     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13164
13165     /* Fudge if we woke up a little too soon */
13166     fudge = intendedTickLength - lastTickLength;
13167     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13168
13169     if (WhiteOnMove(forwardMostMove)) {
13170         if(whiteNPS >= 0) lastTickLength = 0;
13171         timeRemaining = whiteTimeRemaining -= lastTickLength;
13172         DisplayWhiteClock(whiteTimeRemaining - fudge,
13173                           WhiteOnMove(currentMove));
13174     } else {
13175         if(blackNPS >= 0) lastTickLength = 0;
13176         timeRemaining = blackTimeRemaining -= lastTickLength;
13177         DisplayBlackClock(blackTimeRemaining - fudge,
13178                           !WhiteOnMove(currentMove));
13179     }
13180
13181     if (CheckFlags()) return;
13182         
13183     tickStartTM = now;
13184     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13185     StartClockTimer(intendedTickLength);
13186
13187     /* if the time remaining has fallen below the alarm threshold, sound the
13188      * alarm. if the alarm has sounded and (due to a takeback or time control
13189      * with increment) the time remaining has increased to a level above the
13190      * threshold, reset the alarm so it can sound again. 
13191      */
13192     
13193     if (appData.icsActive && appData.icsAlarm) {
13194
13195         /* make sure we are dealing with the user's clock */
13196         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13197                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13198            )) return;
13199
13200         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13201             alarmSounded = FALSE;
13202         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13203             PlayAlarmSound();
13204             alarmSounded = TRUE;
13205         }
13206     }
13207 }
13208
13209
13210 /* A player has just moved, so stop the previously running
13211    clock and (if in clock mode) start the other one.
13212    We redisplay both clocks in case we're in ICS mode, because
13213    ICS gives us an update to both clocks after every move.
13214    Note that this routine is called *after* forwardMostMove
13215    is updated, so the last fractional tick must be subtracted
13216    from the color that is *not* on move now.
13217 */
13218 void
13219 SwitchClocks()
13220 {
13221     long lastTickLength;
13222     TimeMark now;
13223     int flagged = FALSE;
13224
13225     GetTimeMark(&now);
13226
13227     if (StopClockTimer() && appData.clockMode) {
13228         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13229         if (WhiteOnMove(forwardMostMove)) {
13230             if(blackNPS >= 0) lastTickLength = 0;
13231             blackTimeRemaining -= lastTickLength;
13232            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13233 //         if(pvInfoList[forwardMostMove-1].time == -1)
13234                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13235                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13236         } else {
13237            if(whiteNPS >= 0) lastTickLength = 0;
13238            whiteTimeRemaining -= lastTickLength;
13239            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13240 //         if(pvInfoList[forwardMostMove-1].time == -1)
13241                  pvInfoList[forwardMostMove-1].time = 
13242                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13243         }
13244         flagged = CheckFlags();
13245     }
13246     CheckTimeControl();
13247
13248     if (flagged || !appData.clockMode) return;
13249
13250     switch (gameMode) {
13251       case MachinePlaysBlack:
13252       case MachinePlaysWhite:
13253       case BeginningOfGame:
13254         if (pausing) return;
13255         break;
13256
13257       case EditGame:
13258       case PlayFromGameFile:
13259       case IcsExamining:
13260         return;
13261
13262       default:
13263         break;
13264     }
13265
13266     tickStartTM = now;
13267     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13268       whiteTimeRemaining : blackTimeRemaining);
13269     StartClockTimer(intendedTickLength);
13270 }
13271         
13272
13273 /* Stop both clocks */
13274 void
13275 StopClocks()
13276 {       
13277     long lastTickLength;
13278     TimeMark now;
13279
13280     if (!StopClockTimer()) return;
13281     if (!appData.clockMode) return;
13282
13283     GetTimeMark(&now);
13284
13285     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13286     if (WhiteOnMove(forwardMostMove)) {
13287         if(whiteNPS >= 0) lastTickLength = 0;
13288         whiteTimeRemaining -= lastTickLength;
13289         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13290     } else {
13291         if(blackNPS >= 0) lastTickLength = 0;
13292         blackTimeRemaining -= lastTickLength;
13293         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13294     }
13295     CheckFlags();
13296 }
13297         
13298 /* Start clock of player on move.  Time may have been reset, so
13299    if clock is already running, stop and restart it. */
13300 void
13301 StartClocks()
13302 {
13303     (void) StopClockTimer(); /* in case it was running already */
13304     DisplayBothClocks();
13305     if (CheckFlags()) return;
13306
13307     if (!appData.clockMode) return;
13308     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13309
13310     GetTimeMark(&tickStartTM);
13311     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13312       whiteTimeRemaining : blackTimeRemaining);
13313
13314    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13315     whiteNPS = blackNPS = -1; 
13316     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13317        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13318         whiteNPS = first.nps;
13319     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13320        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13321         blackNPS = first.nps;
13322     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13323         whiteNPS = second.nps;
13324     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13325         blackNPS = second.nps;
13326     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13327
13328     StartClockTimer(intendedTickLength);
13329 }
13330
13331 char *
13332 TimeString(ms)
13333      long ms;
13334 {
13335     long second, minute, hour, day;
13336     char *sign = "";
13337     static char buf[32];
13338     
13339     if (ms > 0 && ms <= 9900) {
13340       /* convert milliseconds to tenths, rounding up */
13341       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13342
13343       sprintf(buf, " %03.1f ", tenths/10.0);
13344       return buf;
13345     }
13346
13347     /* convert milliseconds to seconds, rounding up */
13348     /* use floating point to avoid strangeness of integer division
13349        with negative dividends on many machines */
13350     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13351
13352     if (second < 0) {
13353         sign = "-";
13354         second = -second;
13355     }
13356     
13357     day = second / (60 * 60 * 24);
13358     second = second % (60 * 60 * 24);
13359     hour = second / (60 * 60);
13360     second = second % (60 * 60);
13361     minute = second / 60;
13362     second = second % 60;
13363     
13364     if (day > 0)
13365       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13366               sign, day, hour, minute, second);
13367     else if (hour > 0)
13368       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13369     else
13370       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13371     
13372     return buf;
13373 }
13374
13375
13376 /*
13377  * This is necessary because some C libraries aren't ANSI C compliant yet.
13378  */
13379 char *
13380 StrStr(string, match)
13381      char *string, *match;
13382 {
13383     int i, length;
13384     
13385     length = strlen(match);
13386     
13387     for (i = strlen(string) - length; i >= 0; i--, string++)
13388       if (!strncmp(match, string, length))
13389         return string;
13390     
13391     return NULL;
13392 }
13393
13394 char *
13395 StrCaseStr(string, match)
13396      char *string, *match;
13397 {
13398     int i, j, length;
13399     
13400     length = strlen(match);
13401     
13402     for (i = strlen(string) - length; i >= 0; i--, string++) {
13403         for (j = 0; j < length; j++) {
13404             if (ToLower(match[j]) != ToLower(string[j]))
13405               break;
13406         }
13407         if (j == length) return string;
13408     }
13409
13410     return NULL;
13411 }
13412
13413 #ifndef _amigados
13414 int
13415 StrCaseCmp(s1, s2)
13416      char *s1, *s2;
13417 {
13418     char c1, c2;
13419     
13420     for (;;) {
13421         c1 = ToLower(*s1++);
13422         c2 = ToLower(*s2++);
13423         if (c1 > c2) return 1;
13424         if (c1 < c2) return -1;
13425         if (c1 == NULLCHAR) return 0;
13426     }
13427 }
13428
13429
13430 int
13431 ToLower(c)
13432      int c;
13433 {
13434     return isupper(c) ? tolower(c) : c;
13435 }
13436
13437
13438 int
13439 ToUpper(c)
13440      int c;
13441 {
13442     return islower(c) ? toupper(c) : c;
13443 }
13444 #endif /* !_amigados    */
13445
13446 char *
13447 StrSave(s)
13448      char *s;
13449 {
13450     char *ret;
13451
13452     if ((ret = (char *) malloc(strlen(s) + 1))) {
13453         strcpy(ret, s);
13454     }
13455     return ret;
13456 }
13457
13458 char *
13459 StrSavePtr(s, savePtr)
13460      char *s, **savePtr;
13461 {
13462     if (*savePtr) {
13463         free(*savePtr);
13464     }
13465     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13466         strcpy(*savePtr, s);
13467     }
13468     return(*savePtr);
13469 }
13470
13471 char *
13472 PGNDate()
13473 {
13474     time_t clock;
13475     struct tm *tm;
13476     char buf[MSG_SIZ];
13477
13478     clock = time((time_t *)NULL);
13479     tm = localtime(&clock);
13480     sprintf(buf, "%04d.%02d.%02d",
13481             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13482     return StrSave(buf);
13483 }
13484
13485
13486 char *
13487 PositionToFEN(move, overrideCastling)
13488      int move;
13489      char *overrideCastling;
13490 {
13491     int i, j, fromX, fromY, toX, toY;
13492     int whiteToPlay;
13493     char buf[128];
13494     char *p, *q;
13495     int emptycount;
13496     ChessSquare piece;
13497
13498     whiteToPlay = (gameMode == EditPosition) ?
13499       !blackPlaysFirst : (move % 2 == 0);
13500     p = buf;
13501
13502     /* Piece placement data */
13503     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13504         emptycount = 0;
13505         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13506             if (boards[move][i][j] == EmptySquare) {
13507                 emptycount++;
13508             } else { ChessSquare piece = boards[move][i][j];
13509                 if (emptycount > 0) {
13510                     if(emptycount<10) /* [HGM] can be >= 10 */
13511                         *p++ = '0' + emptycount;
13512                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13513                     emptycount = 0;
13514                 }
13515                 if(PieceToChar(piece) == '+') {
13516                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13517                     *p++ = '+';
13518                     piece = (ChessSquare)(DEMOTED piece);
13519                 } 
13520                 *p++ = PieceToChar(piece);
13521                 if(p[-1] == '~') {
13522                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13523                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13524                     *p++ = '~';
13525                 }
13526             }
13527         }
13528         if (emptycount > 0) {
13529             if(emptycount<10) /* [HGM] can be >= 10 */
13530                 *p++ = '0' + emptycount;
13531             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13532             emptycount = 0;
13533         }
13534         *p++ = '/';
13535     }
13536     *(p - 1) = ' ';
13537
13538     /* [HGM] print Crazyhouse or Shogi holdings */
13539     if( gameInfo.holdingsWidth ) {
13540         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13541         q = p;
13542         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13543             piece = boards[move][i][BOARD_WIDTH-1];
13544             if( piece != EmptySquare )
13545               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13546                   *p++ = PieceToChar(piece);
13547         }
13548         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13549             piece = boards[move][BOARD_HEIGHT-i-1][0];
13550             if( piece != EmptySquare )
13551               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13552                   *p++ = PieceToChar(piece);
13553         }
13554
13555         if( q == p ) *p++ = '-';
13556         *p++ = ']';
13557         *p++ = ' ';
13558     }
13559
13560     /* Active color */
13561     *p++ = whiteToPlay ? 'w' : 'b';
13562     *p++ = ' ';
13563
13564   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13565     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13566   } else {
13567   if(nrCastlingRights) {
13568      q = p;
13569      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13570        /* [HGM] write directly from rights */
13571            if(castlingRights[move][2] >= 0 &&
13572               castlingRights[move][0] >= 0   )
13573                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13574            if(castlingRights[move][2] >= 0 &&
13575               castlingRights[move][1] >= 0   )
13576                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13577            if(castlingRights[move][5] >= 0 &&
13578               castlingRights[move][3] >= 0   )
13579                 *p++ = castlingRights[move][3] + AAA;
13580            if(castlingRights[move][5] >= 0 &&
13581               castlingRights[move][4] >= 0   )
13582                 *p++ = castlingRights[move][4] + AAA;
13583      } else {
13584
13585         /* [HGM] write true castling rights */
13586         if( nrCastlingRights == 6 ) {
13587             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13588                castlingRights[move][2] >= 0  ) *p++ = 'K';
13589             if(castlingRights[move][1] == BOARD_LEFT &&
13590                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13591             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13592                castlingRights[move][5] >= 0  ) *p++ = 'k';
13593             if(castlingRights[move][4] == BOARD_LEFT &&
13594                castlingRights[move][5] >= 0  ) *p++ = 'q';
13595         }
13596      }
13597      if (q == p) *p++ = '-'; /* No castling rights */
13598      *p++ = ' ';
13599   }
13600
13601   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13602      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13603     /* En passant target square */
13604     if (move > backwardMostMove) {
13605         fromX = moveList[move - 1][0] - AAA;
13606         fromY = moveList[move - 1][1] - ONE;
13607         toX = moveList[move - 1][2] - AAA;
13608         toY = moveList[move - 1][3] - ONE;
13609         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13610             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13611             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13612             fromX == toX) {
13613             /* 2-square pawn move just happened */
13614             *p++ = toX + AAA;
13615             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13616         } else {
13617             *p++ = '-';
13618         }
13619     } else {
13620         *p++ = '-';
13621     }
13622     *p++ = ' ';
13623   }
13624   }
13625
13626     /* [HGM] find reversible plies */
13627     {   int i = 0, j=move;
13628
13629         if (appData.debugMode) { int k;
13630             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13631             for(k=backwardMostMove; k<=forwardMostMove; k++)
13632                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13633
13634         }
13635
13636         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13637         if( j == backwardMostMove ) i += initialRulePlies;
13638         sprintf(p, "%d ", i);
13639         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13640     }
13641     /* Fullmove number */
13642     sprintf(p, "%d", (move / 2) + 1);
13643     
13644     return StrSave(buf);
13645 }
13646
13647 Boolean
13648 ParseFEN(board, blackPlaysFirst, fen)
13649     Board board;
13650      int *blackPlaysFirst;
13651      char *fen;
13652 {
13653     int i, j;
13654     char *p;
13655     int emptycount;
13656     ChessSquare piece;
13657
13658     p = fen;
13659
13660     /* [HGM] by default clear Crazyhouse holdings, if present */
13661     if(gameInfo.holdingsWidth) {
13662        for(i=0; i<BOARD_HEIGHT; i++) {
13663            board[i][0]             = EmptySquare; /* black holdings */
13664            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13665            board[i][1]             = (ChessSquare) 0; /* black counts */
13666            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13667        }
13668     }
13669
13670     /* Piece placement data */
13671     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13672         j = 0;
13673         for (;;) {
13674             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13675                 if (*p == '/') p++;
13676                 emptycount = gameInfo.boardWidth - j;
13677                 while (emptycount--)
13678                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13679                 break;
13680 #if(BOARD_SIZE >= 10)
13681             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13682                 p++; emptycount=10;
13683                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13684                 while (emptycount--)
13685                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13686 #endif
13687             } else if (isdigit(*p)) {
13688                 emptycount = *p++ - '0';
13689                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13690                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13691                 while (emptycount--)
13692                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13693             } else if (*p == '+' || isalpha(*p)) {
13694                 if (j >= gameInfo.boardWidth) return FALSE;
13695                 if(*p=='+') {
13696                     piece = CharToPiece(*++p);
13697                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13698                     piece = (ChessSquare) (PROMOTED piece ); p++;
13699                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13700                 } else piece = CharToPiece(*p++);
13701
13702                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13703                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13704                     piece = (ChessSquare) (PROMOTED piece);
13705                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13706                     p++;
13707                 }
13708                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13709             } else {
13710                 return FALSE;
13711             }
13712         }
13713     }
13714     while (*p == '/' || *p == ' ') p++;
13715
13716     /* [HGM] look for Crazyhouse holdings here */
13717     while(*p==' ') p++;
13718     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13719         if(*p == '[') p++;
13720         if(*p == '-' ) *p++; /* empty holdings */ else {
13721             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13722             /* if we would allow FEN reading to set board size, we would   */
13723             /* have to add holdings and shift the board read so far here   */
13724             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13725                 *p++;
13726                 if((int) piece >= (int) BlackPawn ) {
13727                     i = (int)piece - (int)BlackPawn;
13728                     i = PieceToNumber((ChessSquare)i);
13729                     if( i >= gameInfo.holdingsSize ) return FALSE;
13730                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13731                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13732                 } else {
13733                     i = (int)piece - (int)WhitePawn;
13734                     i = PieceToNumber((ChessSquare)i);
13735                     if( i >= gameInfo.holdingsSize ) return FALSE;
13736                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13737                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13738                 }
13739             }
13740         }
13741         if(*p == ']') *p++;
13742     }
13743
13744     while(*p == ' ') p++;
13745
13746     /* Active color */
13747     switch (*p++) {
13748       case 'w':
13749         *blackPlaysFirst = FALSE;
13750         break;
13751       case 'b': 
13752         *blackPlaysFirst = TRUE;
13753         break;
13754       default:
13755         return FALSE;
13756     }
13757
13758     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13759     /* return the extra info in global variiables             */
13760
13761     /* set defaults in case FEN is incomplete */
13762     FENepStatus = EP_UNKNOWN;
13763     for(i=0; i<nrCastlingRights; i++ ) {
13764         FENcastlingRights[i] =
13765             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13766     }   /* assume possible unless obviously impossible */
13767     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13768     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13769     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13770     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13771     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13772     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13773     FENrulePlies = 0;
13774
13775     while(*p==' ') p++;
13776     if(nrCastlingRights) {
13777       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13778           /* castling indicator present, so default becomes no castlings */
13779           for(i=0; i<nrCastlingRights; i++ ) {
13780                  FENcastlingRights[i] = -1;
13781           }
13782       }
13783       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13784              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13785              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13786              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13787         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13788
13789         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13790             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13791             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13792         }
13793         switch(c) {
13794           case'K':
13795               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13796               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13797               FENcastlingRights[2] = whiteKingFile;
13798               break;
13799           case'Q':
13800               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13801               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13802               FENcastlingRights[2] = whiteKingFile;
13803               break;
13804           case'k':
13805               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13806               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13807               FENcastlingRights[5] = blackKingFile;
13808               break;
13809           case'q':
13810               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13811               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13812               FENcastlingRights[5] = blackKingFile;
13813           case '-':
13814               break;
13815           default: /* FRC castlings */
13816               if(c >= 'a') { /* black rights */
13817                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13818                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13819                   if(i == BOARD_RGHT) break;
13820                   FENcastlingRights[5] = i;
13821                   c -= AAA;
13822                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13823                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13824                   if(c > i)
13825                       FENcastlingRights[3] = c;
13826                   else
13827                       FENcastlingRights[4] = c;
13828               } else { /* white rights */
13829                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13830                     if(board[0][i] == WhiteKing) break;
13831                   if(i == BOARD_RGHT) break;
13832                   FENcastlingRights[2] = i;
13833                   c -= AAA - 'a' + 'A';
13834                   if(board[0][c] >= WhiteKing) break;
13835                   if(c > i)
13836                       FENcastlingRights[0] = c;
13837                   else
13838                       FENcastlingRights[1] = c;
13839               }
13840         }
13841       }
13842     if (appData.debugMode) {
13843         fprintf(debugFP, "FEN castling rights:");
13844         for(i=0; i<nrCastlingRights; i++)
13845         fprintf(debugFP, " %d", FENcastlingRights[i]);
13846         fprintf(debugFP, "\n");
13847     }
13848
13849       while(*p==' ') p++;
13850     }
13851
13852     /* read e.p. field in games that know e.p. capture */
13853     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13854        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13855       if(*p=='-') {
13856         p++; FENepStatus = EP_NONE;
13857       } else {
13858          char c = *p++ - AAA;
13859
13860          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13861          if(*p >= '0' && *p <='9') *p++;
13862          FENepStatus = c;
13863       }
13864     }
13865
13866
13867     if(sscanf(p, "%d", &i) == 1) {
13868         FENrulePlies = i; /* 50-move ply counter */
13869         /* (The move number is still ignored)    */
13870     }
13871
13872     return TRUE;
13873 }
13874       
13875 void
13876 EditPositionPasteFEN(char *fen)
13877 {
13878   if (fen != NULL) {
13879     Board initial_position;
13880
13881     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13882       DisplayError(_("Bad FEN position in clipboard"), 0);
13883       return ;
13884     } else {
13885       int savedBlackPlaysFirst = blackPlaysFirst;
13886       EditPositionEvent();
13887       blackPlaysFirst = savedBlackPlaysFirst;
13888       CopyBoard(boards[0], initial_position);
13889           /* [HGM] copy FEN attributes as well */
13890           {   int i;
13891               initialRulePlies = FENrulePlies;
13892               epStatus[0] = FENepStatus;
13893               for( i=0; i<nrCastlingRights; i++ )
13894                   castlingRights[0][i] = FENcastlingRights[i];
13895           }
13896       EditPositionDone();
13897       DisplayBothClocks();
13898       DrawPosition(FALSE, boards[currentMove]);
13899     }
13900   }
13901 }