8d016bc0d6c137048b5d6917a0c9f784bfc647c2
[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 int mute; // mute all sounds
473
474 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
475     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
476         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
477     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
478         BlackKing, BlackBishop, BlackKnight, BlackRook }
479 };
480
481 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
482     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
483         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
484     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
485         BlackKing, BlackKing, BlackKnight, BlackRook }
486 };
487
488 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
489     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
490         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
491     { BlackRook, BlackMan, BlackBishop, BlackQueen,
492         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
493 };
494
495 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
496     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
497         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
498     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
499         BlackKing, BlackBishop, BlackKnight, BlackRook }
500 };
501
502 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
503     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
504         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
505     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
506         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
507 };
508
509
510 #if (BOARD_SIZE>=10)
511 ChessSquare ShogiArray[2][BOARD_SIZE] = {
512     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
513         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
514     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
515         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
516 };
517
518 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
519     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
520         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
521     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
522         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
523 };
524
525 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
526     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
527         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
528     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
529         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
530 };
531
532 ChessSquare GreatArray[2][BOARD_SIZE] = {
533     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
534         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
535     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
536         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
537 };
538
539 ChessSquare JanusArray[2][BOARD_SIZE] = {
540     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
541         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
542     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
543         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
544 };
545
546 #ifdef GOTHIC
547 ChessSquare GothicArray[2][BOARD_SIZE] = {
548     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
549         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
550     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
551         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
552 };
553 #else // !GOTHIC
554 #define GothicArray CapablancaArray
555 #endif // !GOTHIC
556
557 #ifdef FALCON
558 ChessSquare FalconArray[2][BOARD_SIZE] = {
559     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
560         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
561     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
562         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
563 };
564 #else // !FALCON
565 #define FalconArray CapablancaArray
566 #endif // !FALCON
567
568 #else // !(BOARD_SIZE>=10)
569 #define XiangqiPosition FIDEArray
570 #define CapablancaArray FIDEArray
571 #define GothicArray FIDEArray
572 #define GreatArray FIDEArray
573 #endif // !(BOARD_SIZE>=10)
574
575 #if (BOARD_SIZE>=12)
576 ChessSquare CourierArray[2][BOARD_SIZE] = {
577     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
578         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
579     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
580         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
581 };
582 #else // !(BOARD_SIZE>=12)
583 #define CourierArray CapablancaArray
584 #endif // !(BOARD_SIZE>=12)
585
586
587 Board initialPosition;
588
589
590 /* Convert str to a rating. Checks for special cases of "----",
591
592    "++++", etc. Also strips ()'s */
593 int
594 string_to_rating(str)
595   char *str;
596 {
597   while(*str && !isdigit(*str)) ++str;
598   if (!*str)
599     return 0;   /* One of the special "no rating" cases */
600   else
601     return atoi(str);
602 }
603
604 void
605 ClearProgramStats()
606 {
607     /* Init programStats */
608     programStats.movelist[0] = 0;
609     programStats.depth = 0;
610     programStats.nr_moves = 0;
611     programStats.moves_left = 0;
612     programStats.nodes = 0;
613     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
614     programStats.score = 0;
615     programStats.got_only_move = 0;
616     programStats.got_fail = 0;
617     programStats.line_is_book = 0;
618 }
619
620 void
621 InitBackEnd1()
622 {
623     int matched, min, sec;
624
625     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
626
627     GetTimeMark(&programStartTime);
628     srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
629
630     ClearProgramStats();
631     programStats.ok_to_send = 1;
632     programStats.seen_stat = 0;
633
634     /*
635      * Initialize game list
636      */
637     ListNew(&gameList);
638
639
640     /*
641      * Internet chess server status
642      */
643     if (appData.icsActive) {
644         appData.matchMode = FALSE;
645         appData.matchGames = 0;
646 #if ZIPPY       
647         appData.noChessProgram = !appData.zippyPlay;
648 #else
649         appData.zippyPlay = FALSE;
650         appData.zippyTalk = FALSE;
651         appData.noChessProgram = TRUE;
652 #endif
653         if (*appData.icsHelper != NULLCHAR) {
654             appData.useTelnet = TRUE;
655             appData.telnetProgram = appData.icsHelper;
656         }
657     } else {
658         appData.zippyTalk = appData.zippyPlay = FALSE;
659     }
660
661     /* [AS] Initialize pv info list [HGM] and game state */
662     {
663         int i, j;
664
665         for( i=0; i<MAX_MOVES; i++ ) {
666             pvInfoList[i].depth = -1;
667             epStatus[i]=EP_NONE;
668             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
669         }
670     }
671
672     /*
673      * Parse timeControl resource
674      */
675     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
676                           appData.movesPerSession)) {
677         char buf[MSG_SIZ];
678         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
679         DisplayFatalError(buf, 0, 2);
680     }
681
682     /*
683      * Parse searchTime resource
684      */
685     if (*appData.searchTime != NULLCHAR) {
686         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
687         if (matched == 1) {
688             searchTime = min * 60;
689         } else if (matched == 2) {
690             searchTime = min * 60 + sec;
691         } else {
692             char buf[MSG_SIZ];
693             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
694             DisplayFatalError(buf, 0, 2);
695         }
696     }
697
698     /* [AS] Adjudication threshold */
699     adjudicateLossThreshold = appData.adjudicateLossThreshold;
700     
701     first.which = "first";
702     second.which = "second";
703     first.maybeThinking = second.maybeThinking = FALSE;
704     first.pr = second.pr = NoProc;
705     first.isr = second.isr = NULL;
706     first.sendTime = second.sendTime = 2;
707     first.sendDrawOffers = 1;
708     if (appData.firstPlaysBlack) {
709         first.twoMachinesColor = "black\n";
710         second.twoMachinesColor = "white\n";
711     } else {
712         first.twoMachinesColor = "white\n";
713         second.twoMachinesColor = "black\n";
714     }
715     first.program = appData.firstChessProgram;
716     second.program = appData.secondChessProgram;
717     first.host = appData.firstHost;
718     second.host = appData.secondHost;
719     first.dir = appData.firstDirectory;
720     second.dir = appData.secondDirectory;
721     first.other = &second;
722     second.other = &first;
723     first.initString = appData.initString;
724     second.initString = appData.secondInitString;
725     first.computerString = appData.firstComputerString;
726     second.computerString = appData.secondComputerString;
727     first.useSigint = second.useSigint = TRUE;
728     first.useSigterm = second.useSigterm = TRUE;
729     first.reuse = appData.reuseFirst;
730     second.reuse = appData.reuseSecond;
731     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
732     second.nps = appData.secondNPS;
733     first.useSetboard = second.useSetboard = FALSE;
734     first.useSAN = second.useSAN = FALSE;
735     first.usePing = second.usePing = FALSE;
736     first.lastPing = second.lastPing = 0;
737     first.lastPong = second.lastPong = 0;
738     first.usePlayother = second.usePlayother = FALSE;
739     first.useColors = second.useColors = TRUE;
740     first.useUsermove = second.useUsermove = FALSE;
741     first.sendICS = second.sendICS = FALSE;
742     first.sendName = second.sendName = appData.icsActive;
743     first.sdKludge = second.sdKludge = FALSE;
744     first.stKludge = second.stKludge = FALSE;
745     TidyProgramName(first.program, first.host, first.tidy);
746     TidyProgramName(second.program, second.host, second.tidy);
747     first.matchWins = second.matchWins = 0;
748     strcpy(first.variants, appData.variant);
749     strcpy(second.variants, appData.variant);
750     first.analysisSupport = second.analysisSupport = 2; /* detect */
751     first.analyzing = second.analyzing = FALSE;
752     first.initDone = second.initDone = FALSE;
753
754     /* New features added by Tord: */
755     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
756     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
757     /* End of new features added by Tord. */
758     first.fenOverride  = appData.fenOverride1;
759     second.fenOverride = appData.fenOverride2;
760
761     /* [HGM] time odds: set factor for each machine */
762     first.timeOdds  = appData.firstTimeOdds;
763     second.timeOdds = appData.secondTimeOdds;
764     { int norm = 1;
765         if(appData.timeOddsMode) {
766             norm = first.timeOdds;
767             if(norm > second.timeOdds) norm = second.timeOdds;
768         }
769         first.timeOdds /= norm;
770         second.timeOdds /= norm;
771     }
772
773     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
774     first.accumulateTC = appData.firstAccumulateTC;
775     second.accumulateTC = appData.secondAccumulateTC;
776     first.maxNrOfSessions = second.maxNrOfSessions = 1;
777
778     /* [HGM] debug */
779     first.debug = second.debug = FALSE;
780     first.supportsNPS = second.supportsNPS = UNKNOWN;
781
782     /* [HGM] options */
783     first.optionSettings  = appData.firstOptions;
784     second.optionSettings = appData.secondOptions;
785
786     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
787     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
788     first.isUCI = appData.firstIsUCI; /* [AS] */
789     second.isUCI = appData.secondIsUCI; /* [AS] */
790     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
791     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
792
793     if (appData.firstProtocolVersion > PROTOVER ||
794         appData.firstProtocolVersion < 1) {
795       char buf[MSG_SIZ];
796       sprintf(buf, _("protocol version %d not supported"),
797               appData.firstProtocolVersion);
798       DisplayFatalError(buf, 0, 2);
799     } else {
800       first.protocolVersion = appData.firstProtocolVersion;
801     }
802
803     if (appData.secondProtocolVersion > PROTOVER ||
804         appData.secondProtocolVersion < 1) {
805       char buf[MSG_SIZ];
806       sprintf(buf, _("protocol version %d not supported"),
807               appData.secondProtocolVersion);
808       DisplayFatalError(buf, 0, 2);
809     } else {
810       second.protocolVersion = appData.secondProtocolVersion;
811     }
812
813     if (appData.icsActive) {
814         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
815     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
816         appData.clockMode = FALSE;
817         first.sendTime = second.sendTime = 0;
818     }
819     
820 #if ZIPPY
821     /* Override some settings from environment variables, for backward
822        compatibility.  Unfortunately it's not feasible to have the env
823        vars just set defaults, at least in xboard.  Ugh.
824     */
825     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
826       ZippyInit();
827     }
828 #endif
829     
830     if (appData.noChessProgram) {
831         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
832         sprintf(programVersion, "%s", PACKAGE_STRING);
833     } else {
834 #if 0
835         char *p, *q;
836         q = first.program;
837         while (*q != ' ' && *q != NULLCHAR) q++;
838         p = q;
839         while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] backslash added */
840         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING + (q - p));
841         sprintf(programVersion, "%s + ", PACKAGE_STRING);
842         strncat(programVersion, p, q - p);
843 #else
844         /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
845         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
846         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
847 #endif
848     }
849
850     if (!appData.icsActive) {
851       char buf[MSG_SIZ];
852       /* Check for variants that are supported only in ICS mode,
853          or not at all.  Some that are accepted here nevertheless
854          have bugs; see comments below.
855       */
856       VariantClass variant = StringToVariant(appData.variant);
857       switch (variant) {
858       case VariantBughouse:     /* need four players and two boards */
859       case VariantKriegspiel:   /* need to hide pieces and move details */
860       /* case VariantFischeRandom: (Fabien: moved below) */
861         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
862         DisplayFatalError(buf, 0, 2);
863         return;
864
865       case VariantUnknown:
866       case VariantLoadable:
867       case Variant29:
868       case Variant30:
869       case Variant31:
870       case Variant32:
871       case Variant33:
872       case Variant34:
873       case Variant35:
874       case Variant36:
875       default:
876         sprintf(buf, _("Unknown variant name %s"), appData.variant);
877         DisplayFatalError(buf, 0, 2);
878         return;
879
880       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
881       case VariantFairy:      /* [HGM] TestLegality definitely off! */
882       case VariantGothic:     /* [HGM] should work */
883       case VariantCapablanca: /* [HGM] should work */
884       case VariantCourier:    /* [HGM] initial forced moves not implemented */
885       case VariantShogi:      /* [HGM] drops not tested for legality */
886       case VariantKnightmate: /* [HGM] should work */
887       case VariantCylinder:   /* [HGM] untested */
888       case VariantFalcon:     /* [HGM] untested */
889       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
890                                  offboard interposition not understood */
891       case VariantNormal:     /* definitely works! */
892       case VariantWildCastle: /* pieces not automatically shuffled */
893       case VariantNoCastle:   /* pieces not automatically shuffled */
894       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
895       case VariantLosers:     /* should work except for win condition,
896                                  and doesn't know captures are mandatory */
897       case VariantSuicide:    /* should work except for win condition,
898                                  and doesn't know captures are mandatory */
899       case VariantGiveaway:   /* should work except for win condition,
900                                  and doesn't know captures are mandatory */
901       case VariantTwoKings:   /* should work */
902       case VariantAtomic:     /* should work except for win condition */
903       case Variant3Check:     /* should work except for win condition */
904       case VariantShatranj:   /* should work except for all win conditions */
905       case VariantBerolina:   /* might work if TestLegality is off */
906       case VariantCapaRandom: /* should work */
907       case VariantJanus:      /* should work */
908       case VariantSuper:      /* experimental */
909       case VariantGreat:      /* experimental, requires legality testing to be off */
910         break;
911       }
912     }
913
914     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
915     InitEngineUCI( installDir, &second );
916 }
917
918 int NextIntegerFromString( char ** str, long * value )
919 {
920     int result = -1;
921     char * s = *str;
922
923     while( *s == ' ' || *s == '\t' ) {
924         s++;
925     }
926
927     *value = 0;
928
929     if( *s >= '0' && *s <= '9' ) {
930         while( *s >= '0' && *s <= '9' ) {
931             *value = *value * 10 + (*s - '0');
932             s++;
933         }
934
935         result = 0;
936     }
937
938     *str = s;
939
940     return result;
941 }
942
943 int NextTimeControlFromString( char ** str, long * value )
944 {
945     long temp;
946     int result = NextIntegerFromString( str, &temp );
947
948     if( result == 0 ) {
949         *value = temp * 60; /* Minutes */
950         if( **str == ':' ) {
951             (*str)++;
952             result = NextIntegerFromString( str, &temp );
953             *value += temp; /* Seconds */
954         }
955     }
956
957     return result;
958 }
959
960 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
961 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
962     int result = -1; long temp, temp2;
963
964     if(**str != '+') return -1; // old params remain in force!
965     (*str)++;
966     if( NextTimeControlFromString( str, &temp ) ) return -1;
967
968     if(**str != '/') {
969         /* time only: incremental or sudden-death time control */
970         if(**str == '+') { /* increment follows; read it */
971             (*str)++;
972             if(result = NextIntegerFromString( str, &temp2)) return -1;
973             *inc = temp2 * 1000;
974         } else *inc = 0;
975         *moves = 0; *tc = temp * 1000; 
976         return 0;
977     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
978
979     (*str)++; /* classical time control */
980     result = NextTimeControlFromString( str, &temp2);
981     if(result == 0) {
982         *moves = temp/60;
983         *tc    = temp2 * 1000;
984         *inc   = 0;
985     }
986     return result;
987 }
988
989 int GetTimeQuota(int movenr)
990 {   /* [HGM] get time to add from the multi-session time-control string */
991     int moves=1; /* kludge to force reading of first session */
992     long time, increment;
993     char *s = fullTimeControlString;
994
995     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
996     do {
997         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
998         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
999         if(movenr == -1) return time;    /* last move before new session     */
1000         if(!moves) return increment;     /* current session is incremental   */
1001         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1002     } while(movenr >= -1);               /* try again for next session       */
1003
1004     return 0; // no new time quota on this move
1005 }
1006
1007 int
1008 ParseTimeControl(tc, ti, mps)
1009      char *tc;
1010      int ti;
1011      int mps;
1012 {
1013 #if 0
1014     int matched, min, sec;
1015
1016     matched = sscanf(tc, "%d:%d", &min, &sec);
1017     if (matched == 1) {
1018         timeControl = min * 60 * 1000;
1019     } else if (matched == 2) {
1020         timeControl = (min * 60 + sec) * 1000;
1021     } else {
1022         return FALSE;
1023     }
1024 #else
1025     long tc1;
1026     long tc2;
1027     char buf[MSG_SIZ];
1028
1029     if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1030     if(ti > 0) {
1031         if(mps)
1032              sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1033         else sprintf(buf, "+%s+%d", tc, ti);
1034     } else {
1035         if(mps)
1036              sprintf(buf, "+%d/%s", mps, tc);
1037         else sprintf(buf, "+%s", tc);
1038     }
1039     fullTimeControlString = StrSave(buf);
1040
1041     if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1042         return FALSE;
1043     }
1044
1045     if( *tc == '/' ) {
1046         /* Parse second time control */
1047         tc++;
1048
1049         if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1050             return FALSE;
1051         }
1052
1053         if( tc2 == 0 ) {
1054             return FALSE;
1055         }
1056
1057         timeControl_2 = tc2 * 1000;
1058     }
1059     else {
1060         timeControl_2 = 0;
1061     }
1062
1063     if( tc1 == 0 ) {
1064         return FALSE;
1065     }
1066
1067     timeControl = tc1 * 1000;
1068 #endif
1069
1070     if (ti >= 0) {
1071         timeIncrement = ti * 1000;  /* convert to ms */
1072         movesPerSession = 0;
1073     } else {
1074         timeIncrement = 0;
1075         movesPerSession = mps;
1076     }
1077     return TRUE;
1078 }
1079
1080 void
1081 InitBackEnd2()
1082 {
1083     if (appData.debugMode) {
1084         fprintf(debugFP, "%s\n", programVersion);
1085     }
1086
1087     if (appData.matchGames > 0) {
1088         appData.matchMode = TRUE;
1089     } else if (appData.matchMode) {
1090         appData.matchGames = 1;
1091     }
1092     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1093         appData.matchGames = appData.sameColorGames;
1094     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1095         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1096         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1097     }
1098     Reset(TRUE, FALSE);
1099     if (appData.noChessProgram || first.protocolVersion == 1) {
1100       InitBackEnd3();
1101     } else {
1102       /* kludge: allow timeout for initial "feature" commands */
1103       FreezeUI();
1104       DisplayMessage("", _("Starting chess program"));
1105       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1106     }
1107 }
1108
1109 void
1110 InitBackEnd3 P((void))
1111 {
1112     GameMode initialMode;
1113     char buf[MSG_SIZ];
1114     int err;
1115
1116     InitChessProgram(&first, startedFromSetupPosition);
1117
1118
1119     if (appData.icsActive) {
1120 #ifdef WIN32
1121         /* [DM] Make a console window if needed [HGM] merged ifs */
1122         ConsoleCreate(); 
1123 #endif
1124         err = establish();
1125         if (err != 0) {
1126             if (*appData.icsCommPort != NULLCHAR) {
1127                 sprintf(buf, _("Could not open comm port %s"),  
1128                         appData.icsCommPort);
1129             } else {
1130                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1131                         appData.icsHost, appData.icsPort);
1132             }
1133             DisplayFatalError(buf, err, 1);
1134             return;
1135         }
1136         SetICSMode();
1137         telnetISR =
1138           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1139         fromUserISR =
1140           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1141     } else if (appData.noChessProgram) {
1142         SetNCPMode();
1143     } else {
1144         SetGNUMode();
1145     }
1146
1147     if (*appData.cmailGameName != NULLCHAR) {
1148         SetCmailMode();
1149         OpenLoopback(&cmailPR);
1150         cmailISR =
1151           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1152     }
1153     
1154     ThawUI();
1155     DisplayMessage("", "");
1156     if (StrCaseCmp(appData.initialMode, "") == 0) {
1157       initialMode = BeginningOfGame;
1158     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1159       initialMode = TwoMachinesPlay;
1160     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1161       initialMode = AnalyzeFile; 
1162     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1163       initialMode = AnalyzeMode;
1164     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1165       initialMode = MachinePlaysWhite;
1166     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1167       initialMode = MachinePlaysBlack;
1168     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1169       initialMode = EditGame;
1170     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1171       initialMode = EditPosition;
1172     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1173       initialMode = Training;
1174     } else {
1175       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1176       DisplayFatalError(buf, 0, 2);
1177       return;
1178     }
1179
1180     if (appData.matchMode) {
1181         /* Set up machine vs. machine match */
1182         if (appData.noChessProgram) {
1183             DisplayFatalError(_("Can't have a match with no chess programs"),
1184                               0, 2);
1185             return;
1186         }
1187         matchMode = TRUE;
1188         matchGame = 1;
1189         if (*appData.loadGameFile != NULLCHAR) {
1190             int index = appData.loadGameIndex; // [HGM] autoinc
1191             if(index<0) lastIndex = index = 1;
1192             if (!LoadGameFromFile(appData.loadGameFile,
1193                                   index,
1194                                   appData.loadGameFile, FALSE)) {
1195                 DisplayFatalError(_("Bad game file"), 0, 1);
1196                 return;
1197             }
1198         } else if (*appData.loadPositionFile != NULLCHAR) {
1199             int index = appData.loadPositionIndex; // [HGM] autoinc
1200             if(index<0) lastIndex = index = 1;
1201             if (!LoadPositionFromFile(appData.loadPositionFile,
1202                                       index,
1203                                       appData.loadPositionFile)) {
1204                 DisplayFatalError(_("Bad position file"), 0, 1);
1205                 return;
1206             }
1207         }
1208         TwoMachinesEvent();
1209     } else if (*appData.cmailGameName != NULLCHAR) {
1210         /* Set up cmail mode */
1211         ReloadCmailMsgEvent(TRUE);
1212     } else {
1213         /* Set up other modes */
1214         if (initialMode == AnalyzeFile) {
1215           if (*appData.loadGameFile == NULLCHAR) {
1216             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1217             return;
1218           }
1219         }
1220         if (*appData.loadGameFile != NULLCHAR) {
1221             (void) LoadGameFromFile(appData.loadGameFile,
1222                                     appData.loadGameIndex,
1223                                     appData.loadGameFile, TRUE);
1224         } else if (*appData.loadPositionFile != NULLCHAR) {
1225             (void) LoadPositionFromFile(appData.loadPositionFile,
1226                                         appData.loadPositionIndex,
1227                                         appData.loadPositionFile);
1228             /* [HGM] try to make self-starting even after FEN load */
1229             /* to allow automatic setup of fairy variants with wtm */
1230             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1231                 gameMode = BeginningOfGame;
1232                 setboardSpoiledMachineBlack = 1;
1233             }
1234             /* [HGM] loadPos: make that every new game uses the setup */
1235             /* from file as long as we do not switch variant          */
1236             if(!blackPlaysFirst) { int i;
1237                 startedFromPositionFile = TRUE;
1238                 CopyBoard(filePosition, boards[0]);
1239                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1240             }
1241         }
1242         if (initialMode == AnalyzeMode) {
1243           if (appData.noChessProgram) {
1244             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1245             return;
1246           }
1247           if (appData.icsActive) {
1248             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1249             return;
1250           }
1251           AnalyzeModeEvent();
1252         } else if (initialMode == AnalyzeFile) {
1253           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1254           ShowThinkingEvent();
1255           AnalyzeFileEvent();
1256           AnalysisPeriodicEvent(1);
1257         } else if (initialMode == MachinePlaysWhite) {
1258           if (appData.noChessProgram) {
1259             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1260                               0, 2);
1261             return;
1262           }
1263           if (appData.icsActive) {
1264             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1265                               0, 2);
1266             return;
1267           }
1268           MachineWhiteEvent();
1269         } else if (initialMode == MachinePlaysBlack) {
1270           if (appData.noChessProgram) {
1271             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1272                               0, 2);
1273             return;
1274           }
1275           if (appData.icsActive) {
1276             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1277                               0, 2);
1278             return;
1279           }
1280           MachineBlackEvent();
1281         } else if (initialMode == TwoMachinesPlay) {
1282           if (appData.noChessProgram) {
1283             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1284                               0, 2);
1285             return;
1286           }
1287           if (appData.icsActive) {
1288             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1289                               0, 2);
1290             return;
1291           }
1292           TwoMachinesEvent();
1293         } else if (initialMode == EditGame) {
1294           EditGameEvent();
1295         } else if (initialMode == EditPosition) {
1296           EditPositionEvent();
1297         } else if (initialMode == Training) {
1298           if (*appData.loadGameFile == NULLCHAR) {
1299             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1300             return;
1301           }
1302           TrainingEvent();
1303         }
1304     }
1305 }
1306
1307 /*
1308  * Establish will establish a contact to a remote host.port.
1309  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1310  *  used to talk to the host.
1311  * Returns 0 if okay, error code if not.
1312  */
1313 int
1314 establish()
1315 {
1316     char buf[MSG_SIZ];
1317
1318     if (*appData.icsCommPort != NULLCHAR) {
1319         /* Talk to the host through a serial comm port */
1320         return OpenCommPort(appData.icsCommPort, &icsPR);
1321
1322     } else if (*appData.gateway != NULLCHAR) {
1323         if (*appData.remoteShell == NULLCHAR) {
1324             /* Use the rcmd protocol to run telnet program on a gateway host */
1325             snprintf(buf, sizeof(buf), "%s %s %s",
1326                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1327             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1328
1329         } else {
1330             /* Use the rsh program to run telnet program on a gateway host */
1331             if (*appData.remoteUser == NULLCHAR) {
1332                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1333                         appData.gateway, appData.telnetProgram,
1334                         appData.icsHost, appData.icsPort);
1335             } else {
1336                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1337                         appData.remoteShell, appData.gateway, 
1338                         appData.remoteUser, appData.telnetProgram,
1339                         appData.icsHost, appData.icsPort);
1340             }
1341             return StartChildProcess(buf, "", &icsPR);
1342
1343         }
1344     } else if (appData.useTelnet) {
1345         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1346
1347     } else {
1348         /* TCP socket interface differs somewhat between
1349            Unix and NT; handle details in the front end.
1350            */
1351         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1352     }
1353 }
1354
1355 void
1356 show_bytes(fp, buf, count)
1357      FILE *fp;
1358      char *buf;
1359      int count;
1360 {
1361     while (count--) {
1362         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1363             fprintf(fp, "\\%03o", *buf & 0xff);
1364         } else {
1365             putc(*buf, fp);
1366         }
1367         buf++;
1368     }
1369     fflush(fp);
1370 }
1371
1372 /* Returns an errno value */
1373 int
1374 OutputMaybeTelnet(pr, message, count, outError)
1375      ProcRef pr;
1376      char *message;
1377      int count;
1378      int *outError;
1379 {
1380     char buf[8192], *p, *q, *buflim;
1381     int left, newcount, outcount;
1382
1383     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1384         *appData.gateway != NULLCHAR) {
1385         if (appData.debugMode) {
1386             fprintf(debugFP, ">ICS: ");
1387             show_bytes(debugFP, message, count);
1388             fprintf(debugFP, "\n");
1389         }
1390         return OutputToProcess(pr, message, count, outError);
1391     }
1392
1393     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1394     p = message;
1395     q = buf;
1396     left = count;
1397     newcount = 0;
1398     while (left) {
1399         if (q >= buflim) {
1400             if (appData.debugMode) {
1401                 fprintf(debugFP, ">ICS: ");
1402                 show_bytes(debugFP, buf, newcount);
1403                 fprintf(debugFP, "\n");
1404             }
1405             outcount = OutputToProcess(pr, buf, newcount, outError);
1406             if (outcount < newcount) return -1; /* to be sure */
1407             q = buf;
1408             newcount = 0;
1409         }
1410         if (*p == '\n') {
1411             *q++ = '\r';
1412             newcount++;
1413         } else if (((unsigned char) *p) == TN_IAC) {
1414             *q++ = (char) TN_IAC;
1415             newcount ++;
1416         }
1417         *q++ = *p++;
1418         newcount++;
1419         left--;
1420     }
1421     if (appData.debugMode) {
1422         fprintf(debugFP, ">ICS: ");
1423         show_bytes(debugFP, buf, newcount);
1424         fprintf(debugFP, "\n");
1425     }
1426     outcount = OutputToProcess(pr, buf, newcount, outError);
1427     if (outcount < newcount) return -1; /* to be sure */
1428     return count;
1429 }
1430
1431 void
1432 read_from_player(isr, closure, message, count, error)
1433      InputSourceRef isr;
1434      VOIDSTAR closure;
1435      char *message;
1436      int count;
1437      int error;
1438 {
1439     int outError, outCount;
1440     static int gotEof = 0;
1441
1442     /* Pass data read from player on to ICS */
1443     if (count > 0) {
1444         gotEof = 0;
1445         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1446         if (outCount < count) {
1447             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1448         }
1449     } else if (count < 0) {
1450         RemoveInputSource(isr);
1451         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1452     } else if (gotEof++ > 0) {
1453         RemoveInputSource(isr);
1454         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1455     }
1456 }
1457
1458 void
1459 KeepAlive()
1460 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1461     SendToICS("date\n");
1462     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1463 }
1464
1465 void
1466 SendToICS(s)
1467      char *s;
1468 {
1469     int count, outCount, outError;
1470
1471     if (icsPR == NULL) return;
1472
1473     count = strlen(s);
1474     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1475     if (outCount < count) {
1476         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1477     }
1478 }
1479
1480 /* This is used for sending logon scripts to the ICS. Sending
1481    without a delay causes problems when using timestamp on ICC
1482    (at least on my machine). */
1483 void
1484 SendToICSDelayed(s,msdelay)
1485      char *s;
1486      long msdelay;
1487 {
1488     int count, outCount, outError;
1489
1490     if (icsPR == NULL) return;
1491
1492     count = strlen(s);
1493     if (appData.debugMode) {
1494         fprintf(debugFP, ">ICS: ");
1495         show_bytes(debugFP, s, count);
1496         fprintf(debugFP, "\n");
1497     }
1498     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1499                                       msdelay);
1500     if (outCount < count) {
1501         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1502     }
1503 }
1504
1505
1506 /* Remove all highlighting escape sequences in s
1507    Also deletes any suffix starting with '(' 
1508    */
1509 char *
1510 StripHighlightAndTitle(s)
1511      char *s;
1512 {
1513     static char retbuf[MSG_SIZ];
1514     char *p = retbuf;
1515
1516     while (*s != NULLCHAR) {
1517         while (*s == '\033') {
1518             while (*s != NULLCHAR && !isalpha(*s)) s++;
1519             if (*s != NULLCHAR) s++;
1520         }
1521         while (*s != NULLCHAR && *s != '\033') {
1522             if (*s == '(' || *s == '[') {
1523                 *p = NULLCHAR;
1524                 return retbuf;
1525             }
1526             *p++ = *s++;
1527         }
1528     }
1529     *p = NULLCHAR;
1530     return retbuf;
1531 }
1532
1533 /* Remove all highlighting escape sequences in s */
1534 char *
1535 StripHighlight(s)
1536      char *s;
1537 {
1538     static char retbuf[MSG_SIZ];
1539     char *p = retbuf;
1540
1541     while (*s != NULLCHAR) {
1542         while (*s == '\033') {
1543             while (*s != NULLCHAR && !isalpha(*s)) s++;
1544             if (*s != NULLCHAR) s++;
1545         }
1546         while (*s != NULLCHAR && *s != '\033') {
1547             *p++ = *s++;
1548         }
1549     }
1550     *p = NULLCHAR;
1551     return retbuf;
1552 }
1553
1554 char *variantNames[] = VARIANT_NAMES;
1555 char *
1556 VariantName(v)
1557      VariantClass v;
1558 {
1559     return variantNames[v];
1560 }
1561
1562
1563 /* Identify a variant from the strings the chess servers use or the
1564    PGN Variant tag names we use. */
1565 VariantClass
1566 StringToVariant(e)
1567      char *e;
1568 {
1569     char *p;
1570     int wnum = -1;
1571     VariantClass v = VariantNormal;
1572     int i, found = FALSE;
1573     char buf[MSG_SIZ];
1574
1575     if (!e) return v;
1576
1577     /* [HGM] skip over optional board-size prefixes */
1578     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1579         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1580         while( *e++ != '_');
1581     }
1582
1583     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1584       if (StrCaseStr(e, variantNames[i])) {
1585         v = (VariantClass) i;
1586         found = TRUE;
1587         break;
1588       }
1589     }
1590
1591     if (!found) {
1592       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1593           || StrCaseStr(e, "wild/fr") 
1594           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1595         v = VariantFischeRandom;
1596       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1597                  (i = 1, p = StrCaseStr(e, "w"))) {
1598         p += i;
1599         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1600         if (isdigit(*p)) {
1601           wnum = atoi(p);
1602         } else {
1603           wnum = -1;
1604         }
1605         switch (wnum) {
1606         case 0: /* FICS only, actually */
1607         case 1:
1608           /* Castling legal even if K starts on d-file */
1609           v = VariantWildCastle;
1610           break;
1611         case 2:
1612         case 3:
1613         case 4:
1614           /* Castling illegal even if K & R happen to start in
1615              normal positions. */
1616           v = VariantNoCastle;
1617           break;
1618         case 5:
1619         case 7:
1620         case 8:
1621         case 10:
1622         case 11:
1623         case 12:
1624         case 13:
1625         case 14:
1626         case 15:
1627         case 18:
1628         case 19:
1629           /* Castling legal iff K & R start in normal positions */
1630           v = VariantNormal;
1631           break;
1632         case 6:
1633         case 20:
1634         case 21:
1635           /* Special wilds for position setup; unclear what to do here */
1636           v = VariantLoadable;
1637           break;
1638         case 9:
1639           /* Bizarre ICC game */
1640           v = VariantTwoKings;
1641           break;
1642         case 16:
1643           v = VariantKriegspiel;
1644           break;
1645         case 17:
1646           v = VariantLosers;
1647           break;
1648         case 22:
1649           v = VariantFischeRandom;
1650           break;
1651         case 23:
1652           v = VariantCrazyhouse;
1653           break;
1654         case 24:
1655           v = VariantBughouse;
1656           break;
1657         case 25:
1658           v = Variant3Check;
1659           break;
1660         case 26:
1661           /* Not quite the same as FICS suicide! */
1662           v = VariantGiveaway;
1663           break;
1664         case 27:
1665           v = VariantAtomic;
1666           break;
1667         case 28:
1668           v = VariantShatranj;
1669           break;
1670
1671         /* Temporary names for future ICC types.  The name *will* change in 
1672            the next xboard/WinBoard release after ICC defines it. */
1673         case 29:
1674           v = Variant29;
1675           break;
1676         case 30:
1677           v = Variant30;
1678           break;
1679         case 31:
1680           v = Variant31;
1681           break;
1682         case 32:
1683           v = Variant32;
1684           break;
1685         case 33:
1686           v = Variant33;
1687           break;
1688         case 34:
1689           v = Variant34;
1690           break;
1691         case 35:
1692           v = Variant35;
1693           break;
1694         case 36:
1695           v = Variant36;
1696           break;
1697         case 37:
1698           v = VariantShogi;
1699           break;
1700         case 38:
1701           v = VariantXiangqi;
1702           break;
1703         case 39:
1704           v = VariantCourier;
1705           break;
1706         case 40:
1707           v = VariantGothic;
1708           break;
1709         case 41:
1710           v = VariantCapablanca;
1711           break;
1712         case 42:
1713           v = VariantKnightmate;
1714           break;
1715         case 43:
1716           v = VariantFairy;
1717           break;
1718         case 44:
1719           v = VariantCylinder;
1720           break;
1721         case 45:
1722           v = VariantFalcon;
1723           break;
1724         case 46:
1725           v = VariantCapaRandom;
1726           break;
1727         case 47:
1728           v = VariantBerolina;
1729           break;
1730         case 48:
1731           v = VariantJanus;
1732           break;
1733         case 49:
1734           v = VariantSuper;
1735           break;
1736         case 50:
1737           v = VariantGreat;
1738           break;
1739         case -1:
1740           /* Found "wild" or "w" in the string but no number;
1741              must assume it's normal chess. */
1742           v = VariantNormal;
1743           break;
1744         default:
1745           sprintf(buf, _("Unknown wild type %d"), wnum);
1746           DisplayError(buf, 0);
1747           v = VariantUnknown;
1748           break;
1749         }
1750       }
1751     }
1752     if (appData.debugMode) {
1753       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1754               e, wnum, VariantName(v));
1755     }
1756     return v;
1757 }
1758
1759 static int leftover_start = 0, leftover_len = 0;
1760 char star_match[STAR_MATCH_N][MSG_SIZ];
1761
1762 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1763    advance *index beyond it, and set leftover_start to the new value of
1764    *index; else return FALSE.  If pattern contains the character '*', it
1765    matches any sequence of characters not containing '\r', '\n', or the
1766    character following the '*' (if any), and the matched sequence(s) are
1767    copied into star_match.
1768    */
1769 int
1770 looking_at(buf, index, pattern)
1771      char *buf;
1772      int *index;
1773      char *pattern;
1774 {
1775     char *bufp = &buf[*index], *patternp = pattern;
1776     int star_count = 0;
1777     char *matchp = star_match[0];
1778     
1779     for (;;) {
1780         if (*patternp == NULLCHAR) {
1781             *index = leftover_start = bufp - buf;
1782             *matchp = NULLCHAR;
1783             return TRUE;
1784         }
1785         if (*bufp == NULLCHAR) return FALSE;
1786         if (*patternp == '*') {
1787             if (*bufp == *(patternp + 1)) {
1788                 *matchp = NULLCHAR;
1789                 matchp = star_match[++star_count];
1790                 patternp += 2;
1791                 bufp++;
1792                 continue;
1793             } else if (*bufp == '\n' || *bufp == '\r') {
1794                 patternp++;
1795                 if (*patternp == NULLCHAR)
1796                   continue;
1797                 else
1798                   return FALSE;
1799             } else {
1800                 *matchp++ = *bufp++;
1801                 continue;
1802             }
1803         }
1804         if (*patternp != *bufp) return FALSE;
1805         patternp++;
1806         bufp++;
1807     }
1808 }
1809
1810 void
1811 SendToPlayer(data, length)
1812      char *data;
1813      int length;
1814 {
1815     int error, outCount;
1816     outCount = OutputToProcess(NoProc, data, length, &error);
1817     if (outCount < length) {
1818         DisplayFatalError(_("Error writing to display"), error, 1);
1819     }
1820 }
1821
1822 void
1823 PackHolding(packed, holding)
1824      char packed[];
1825      char *holding;
1826 {
1827     char *p = holding;
1828     char *q = packed;
1829     int runlength = 0;
1830     int curr = 9999;
1831     do {
1832         if (*p == curr) {
1833             runlength++;
1834         } else {
1835             switch (runlength) {
1836               case 0:
1837                 break;
1838               case 1:
1839                 *q++ = curr;
1840                 break;
1841               case 2:
1842                 *q++ = curr;
1843                 *q++ = curr;
1844                 break;
1845               default:
1846                 sprintf(q, "%d", runlength);
1847                 while (*q) q++;
1848                 *q++ = curr;
1849                 break;
1850             }
1851             runlength = 1;
1852             curr = *p;
1853         }
1854     } while (*p++);
1855     *q = NULLCHAR;
1856 }
1857
1858 /* Telnet protocol requests from the front end */
1859 void
1860 TelnetRequest(ddww, option)
1861      unsigned char ddww, option;
1862 {
1863     unsigned char msg[3];
1864     int outCount, outError;
1865
1866     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1867
1868     if (appData.debugMode) {
1869         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1870         switch (ddww) {
1871           case TN_DO:
1872             ddwwStr = "DO";
1873             break;
1874           case TN_DONT:
1875             ddwwStr = "DONT";
1876             break;
1877           case TN_WILL:
1878             ddwwStr = "WILL";
1879             break;
1880           case TN_WONT:
1881             ddwwStr = "WONT";
1882             break;
1883           default:
1884             ddwwStr = buf1;
1885             sprintf(buf1, "%d", ddww);
1886             break;
1887         }
1888         switch (option) {
1889           case TN_ECHO:
1890             optionStr = "ECHO";
1891             break;
1892           default:
1893             optionStr = buf2;
1894             sprintf(buf2, "%d", option);
1895             break;
1896         }
1897         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1898     }
1899     msg[0] = TN_IAC;
1900     msg[1] = ddww;
1901     msg[2] = option;
1902     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1903     if (outCount < 3) {
1904         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1905     }
1906 }
1907
1908 void
1909 DoEcho()
1910 {
1911     if (!appData.icsActive) return;
1912     TelnetRequest(TN_DO, TN_ECHO);
1913 }
1914
1915 void
1916 DontEcho()
1917 {
1918     if (!appData.icsActive) return;
1919     TelnetRequest(TN_DONT, TN_ECHO);
1920 }
1921
1922 void
1923 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1924 {
1925     /* put the holdings sent to us by the server on the board holdings area */
1926     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1927     char p;
1928     ChessSquare piece;
1929
1930     if(gameInfo.holdingsWidth < 2)  return;
1931
1932     if( (int)lowestPiece >= BlackPawn ) {
1933         holdingsColumn = 0;
1934         countsColumn = 1;
1935         holdingsStartRow = BOARD_HEIGHT-1;
1936         direction = -1;
1937     } else {
1938         holdingsColumn = BOARD_WIDTH-1;
1939         countsColumn = BOARD_WIDTH-2;
1940         holdingsStartRow = 0;
1941         direction = 1;
1942     }
1943
1944     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1945         board[i][holdingsColumn] = EmptySquare;
1946         board[i][countsColumn]   = (ChessSquare) 0;
1947     }
1948     while( (p=*holdings++) != NULLCHAR ) {
1949         piece = CharToPiece( ToUpper(p) );
1950         if(piece == EmptySquare) continue;
1951         /*j = (int) piece - (int) WhitePawn;*/
1952         j = PieceToNumber(piece);
1953         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1954         if(j < 0) continue;               /* should not happen */
1955         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1956         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1957         board[holdingsStartRow+j*direction][countsColumn]++;
1958     }
1959
1960 }
1961
1962
1963 void
1964 VariantSwitch(Board board, VariantClass newVariant)
1965 {
1966    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1967    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1968 //   Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1969
1970    startedFromPositionFile = FALSE;
1971    if(gameInfo.variant == newVariant) return;
1972
1973    /* [HGM] This routine is called each time an assignment is made to
1974     * gameInfo.variant during a game, to make sure the board sizes
1975     * are set to match the new variant. If that means adding or deleting
1976     * holdings, we shift the playing board accordingly
1977     * This kludge is needed because in ICS observe mode, we get boards
1978     * of an ongoing game without knowing the variant, and learn about the
1979     * latter only later. This can be because of the move list we requested,
1980     * in which case the game history is refilled from the beginning anyway,
1981     * but also when receiving holdings of a crazyhouse game. In the latter
1982     * case we want to add those holdings to the already received position.
1983     */
1984
1985
1986   if (appData.debugMode) {
1987     fprintf(debugFP, "Switch board from %s to %s\n",
1988                VariantName(gameInfo.variant), VariantName(newVariant));
1989     setbuf(debugFP, NULL);
1990   }
1991     shuffleOpenings = 0;       /* [HGM] shuffle */
1992     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1993     switch(newVariant) {
1994             case VariantShogi:
1995               newWidth = 9;  newHeight = 9;
1996               gameInfo.holdingsSize = 7;
1997             case VariantBughouse:
1998             case VariantCrazyhouse:
1999               newHoldingsWidth = 2; break;
2000             default:
2001               newHoldingsWidth = gameInfo.holdingsSize = 0;
2002     }
2003
2004     if(newWidth  != gameInfo.boardWidth  ||
2005        newHeight != gameInfo.boardHeight ||
2006        newHoldingsWidth != gameInfo.holdingsWidth ) {
2007
2008         /* shift position to new playing area, if needed */
2009         if(newHoldingsWidth > gameInfo.holdingsWidth) {
2010            for(i=0; i<BOARD_HEIGHT; i++) 
2011                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2012                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2013                                                      board[i][j];
2014            for(i=0; i<newHeight; i++) {
2015                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2016                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2017            }
2018         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2019            for(i=0; i<BOARD_HEIGHT; i++)
2020                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2021                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2022                                                  board[i][j];
2023         }
2024
2025         gameInfo.boardWidth  = newWidth;
2026         gameInfo.boardHeight = newHeight;
2027         gameInfo.holdingsWidth = newHoldingsWidth;
2028         gameInfo.variant = newVariant;
2029         InitDrawingSizes(-2, 0);
2030
2031         /* [HGM] The following should definitely be solved in a better way */
2032 #if 0
2033         CopyBoard(board, tempBoard); /* save position in case it is board[0] */
2034         for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
2035         saveEP = epStatus[0];
2036 #endif
2037         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2038 #if 0
2039         epStatus[0] = saveEP;
2040         for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
2041         CopyBoard(tempBoard, board); /* restore position received from ICS   */
2042 #endif
2043     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2044
2045     forwardMostMove = oldForwardMostMove;
2046     backwardMostMove = oldBackwardMostMove;
2047     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2048 }
2049
2050 static int loggedOn = FALSE;
2051
2052 /*-- Game start info cache: --*/
2053 int gs_gamenum;
2054 char gs_kind[MSG_SIZ];
2055 static char player1Name[128] = "";
2056 static char player2Name[128] = "";
2057 static int player1Rating = -1;
2058 static int player2Rating = -1;
2059 /*----------------------------*/
2060
2061 ColorClass curColor = ColorNormal;
2062 int suppressKibitz = 0;
2063
2064 void
2065 read_from_ics(isr, closure, data, count, error)
2066      InputSourceRef isr;
2067      VOIDSTAR closure;
2068      char *data;
2069      int count;
2070      int error;
2071 {
2072 #define BUF_SIZE 8192
2073 #define STARTED_NONE 0
2074 #define STARTED_MOVES 1
2075 #define STARTED_BOARD 2
2076 #define STARTED_OBSERVE 3
2077 #define STARTED_HOLDINGS 4
2078 #define STARTED_CHATTER 5
2079 #define STARTED_COMMENT 6
2080 #define STARTED_MOVES_NOHIDE 7
2081     
2082     static int started = STARTED_NONE;
2083     static char parse[20000];
2084     static int parse_pos = 0;
2085     static char buf[BUF_SIZE + 1];
2086     static int firstTime = TRUE, intfSet = FALSE;
2087     static ColorClass prevColor = ColorNormal;
2088     static int savingComment = FALSE;
2089     char str[500];
2090     int i, oldi;
2091     int buf_len;
2092     int next_out;
2093     int tkind;
2094     int backup;    /* [DM] For zippy color lines */
2095     char *p;
2096     char talker[MSG_SIZ]; // [HGM] chat
2097     int channel;
2098
2099     if (appData.debugMode) {
2100       if (!error) {
2101         fprintf(debugFP, "<ICS: ");
2102         show_bytes(debugFP, data, count);
2103         fprintf(debugFP, "\n");
2104       }
2105     }
2106
2107     if (appData.debugMode) { int f = forwardMostMove;
2108         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2109                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2110     }
2111     if (count > 0) {
2112         /* If last read ended with a partial line that we couldn't parse,
2113            prepend it to the new read and try again. */
2114         if (leftover_len > 0) {
2115             for (i=0; i<leftover_len; i++)
2116               buf[i] = buf[leftover_start + i];
2117         }
2118
2119         /* Copy in new characters, removing nulls and \r's */
2120         buf_len = leftover_len;
2121         for (i = 0; i < count; i++) {
2122             if (data[i] != NULLCHAR && data[i] != '\r')
2123               buf[buf_len++] = data[i];
2124             if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && 
2125                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') {
2126                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2127                 buf[buf_len++] = ' '; // replace by space (assumes ICS does not break lines within word)
2128             }
2129         }
2130
2131         buf[buf_len] = NULLCHAR;
2132         next_out = leftover_len;
2133         leftover_start = 0;
2134         
2135         i = 0;
2136         while (i < buf_len) {
2137             /* Deal with part of the TELNET option negotiation
2138                protocol.  We refuse to do anything beyond the
2139                defaults, except that we allow the WILL ECHO option,
2140                which ICS uses to turn off password echoing when we are
2141                directly connected to it.  We reject this option
2142                if localLineEditing mode is on (always on in xboard)
2143                and we are talking to port 23, which might be a real
2144                telnet server that will try to keep WILL ECHO on permanently.
2145              */
2146             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2147                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2148                 unsigned char option;
2149                 oldi = i;
2150                 switch ((unsigned char) buf[++i]) {
2151                   case TN_WILL:
2152                     if (appData.debugMode)
2153                       fprintf(debugFP, "\n<WILL ");
2154                     switch (option = (unsigned char) buf[++i]) {
2155                       case TN_ECHO:
2156                         if (appData.debugMode)
2157                           fprintf(debugFP, "ECHO ");
2158                         /* Reply only if this is a change, according
2159                            to the protocol rules. */
2160                         if (remoteEchoOption) break;
2161                         if (appData.localLineEditing &&
2162                             atoi(appData.icsPort) == TN_PORT) {
2163                             TelnetRequest(TN_DONT, TN_ECHO);
2164                         } else {
2165                             EchoOff();
2166                             TelnetRequest(TN_DO, TN_ECHO);
2167                             remoteEchoOption = TRUE;
2168                         }
2169                         break;
2170                       default:
2171                         if (appData.debugMode)
2172                           fprintf(debugFP, "%d ", option);
2173                         /* Whatever this is, we don't want it. */
2174                         TelnetRequest(TN_DONT, option);
2175                         break;
2176                     }
2177                     break;
2178                   case TN_WONT:
2179                     if (appData.debugMode)
2180                       fprintf(debugFP, "\n<WONT ");
2181                     switch (option = (unsigned char) buf[++i]) {
2182                       case TN_ECHO:
2183                         if (appData.debugMode)
2184                           fprintf(debugFP, "ECHO ");
2185                         /* Reply only if this is a change, according
2186                            to the protocol rules. */
2187                         if (!remoteEchoOption) break;
2188                         EchoOn();
2189                         TelnetRequest(TN_DONT, TN_ECHO);
2190                         remoteEchoOption = FALSE;
2191                         break;
2192                       default:
2193                         if (appData.debugMode)
2194                           fprintf(debugFP, "%d ", (unsigned char) option);
2195                         /* Whatever this is, it must already be turned
2196                            off, because we never agree to turn on
2197                            anything non-default, so according to the
2198                            protocol rules, we don't reply. */
2199                         break;
2200                     }
2201                     break;
2202                   case TN_DO:
2203                     if (appData.debugMode)
2204                       fprintf(debugFP, "\n<DO ");
2205                     switch (option = (unsigned char) buf[++i]) {
2206                       default:
2207                         /* Whatever this is, we refuse to do it. */
2208                         if (appData.debugMode)
2209                           fprintf(debugFP, "%d ", option);
2210                         TelnetRequest(TN_WONT, option);
2211                         break;
2212                     }
2213                     break;
2214                   case TN_DONT:
2215                     if (appData.debugMode)
2216                       fprintf(debugFP, "\n<DONT ");
2217                     switch (option = (unsigned char) buf[++i]) {
2218                       default:
2219                         if (appData.debugMode)
2220                           fprintf(debugFP, "%d ", option);
2221                         /* Whatever this is, we are already not doing
2222                            it, because we never agree to do anything
2223                            non-default, so according to the protocol
2224                            rules, we don't reply. */
2225                         break;
2226                     }
2227                     break;
2228                   case TN_IAC:
2229                     if (appData.debugMode)
2230                       fprintf(debugFP, "\n<IAC ");
2231                     /* Doubled IAC; pass it through */
2232                     i--;
2233                     break;
2234                   default:
2235                     if (appData.debugMode)
2236                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2237                     /* Drop all other telnet commands on the floor */
2238                     break;
2239                 }
2240                 if (oldi > next_out)
2241                   SendToPlayer(&buf[next_out], oldi - next_out);
2242                 if (++i > next_out)
2243                   next_out = i;
2244                 continue;
2245             }
2246                 
2247             /* OK, this at least will *usually* work */
2248             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2249                 loggedOn = TRUE;
2250             }
2251             
2252             if (loggedOn && !intfSet) {
2253                 if (ics_type == ICS_ICC) {
2254                   sprintf(str,
2255                           "/set-quietly interface %s\n/set-quietly style 12\n",
2256                           programVersion);
2257
2258                 } else if (ics_type == ICS_CHESSNET) {
2259                   sprintf(str, "/style 12\n");
2260                 } else {
2261                   strcpy(str, "alias $ @\n$set interface ");
2262                   strcat(str, programVersion);
2263                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2264 #ifdef WIN32
2265                   strcat(str, "$iset nohighlight 1\n");
2266 #endif
2267                   strcat(str, "$iset lock 1\n$style 12\n");
2268                 }
2269                 SendToICS(str);
2270                 intfSet = TRUE;
2271             }
2272
2273             if (started == STARTED_COMMENT) {
2274                 /* Accumulate characters in comment */
2275                 parse[parse_pos++] = buf[i];
2276                 if (buf[i] == '\n') {
2277                     parse[parse_pos] = NULLCHAR;
2278                     if(chattingPartner>=0) {
2279                         char mess[MSG_SIZ];
2280                         sprintf(mess, "%s%s", talker, parse);
2281                         OutputChatMessage(chattingPartner, mess);
2282                         chattingPartner = -1;
2283                     } else
2284                     if(!suppressKibitz) // [HGM] kibitz
2285                         AppendComment(forwardMostMove, StripHighlight(parse));
2286                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2287                         int nrDigit = 0, nrAlph = 0, i;
2288                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2289                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2290                         parse[parse_pos] = NULLCHAR;
2291                         // try to be smart: if it does not look like search info, it should go to
2292                         // ICS interaction window after all, not to engine-output window.
2293                         for(i=0; i<parse_pos; i++) { // count letters and digits
2294                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2295                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2296                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2297                         }
2298                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2299                             int depth=0; float score;
2300                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2301                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2302                                 pvInfoList[forwardMostMove-1].depth = depth;
2303                                 pvInfoList[forwardMostMove-1].score = 100*score;
2304                             }
2305                             OutputKibitz(suppressKibitz, parse);
2306                         } else {
2307                             char tmp[MSG_SIZ];
2308                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2309                             SendToPlayer(tmp, strlen(tmp));
2310                         }
2311                     }
2312                     started = STARTED_NONE;
2313                 } else {
2314                     /* Don't match patterns against characters in chatter */
2315                     i++;
2316                     continue;
2317                 }
2318             }
2319             if (started == STARTED_CHATTER) {
2320                 if (buf[i] != '\n') {
2321                     /* Don't match patterns against characters in chatter */
2322                     i++;
2323                     continue;
2324                 }
2325                 started = STARTED_NONE;
2326             }
2327
2328             /* Kludge to deal with rcmd protocol */
2329             if (firstTime && looking_at(buf, &i, "\001*")) {
2330                 DisplayFatalError(&buf[1], 0, 1);
2331                 continue;
2332             } else {
2333                 firstTime = FALSE;
2334             }
2335
2336             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2337                 ics_type = ICS_ICC;
2338                 ics_prefix = "/";
2339                 if (appData.debugMode)
2340                   fprintf(debugFP, "ics_type %d\n", ics_type);
2341                 continue;
2342             }
2343             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2344                 ics_type = ICS_FICS;
2345                 ics_prefix = "$";
2346                 if (appData.debugMode)
2347                   fprintf(debugFP, "ics_type %d\n", ics_type);
2348                 continue;
2349             }
2350             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2351                 ics_type = ICS_CHESSNET;
2352                 ics_prefix = "/";
2353                 if (appData.debugMode)
2354                   fprintf(debugFP, "ics_type %d\n", ics_type);
2355                 continue;
2356             }
2357
2358             if (!loggedOn &&
2359                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2360                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2361                  looking_at(buf, &i, "will be \"*\""))) {
2362               strcpy(ics_handle, star_match[0]);
2363               continue;
2364             }
2365
2366             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2367               char buf[MSG_SIZ];
2368               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2369               DisplayIcsInteractionTitle(buf);
2370               have_set_title = TRUE;
2371             }
2372
2373             /* skip finger notes */
2374             if (started == STARTED_NONE &&
2375                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2376                  (buf[i] == '1' && buf[i+1] == '0')) &&
2377                 buf[i+2] == ':' && buf[i+3] == ' ') {
2378               started = STARTED_CHATTER;
2379               i += 3;
2380               continue;
2381             }
2382
2383             /* skip formula vars */
2384             if (started == STARTED_NONE &&
2385                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2386               started = STARTED_CHATTER;
2387               i += 3;
2388               continue;
2389             }
2390
2391             oldi = i;
2392             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2393             if (appData.autoKibitz && started == STARTED_NONE && 
2394                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2395                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2396                 if(looking_at(buf, &i, "* kibitzes: ") &&
2397                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2398                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2399                         suppressKibitz = TRUE;
2400                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2401                                 && (gameMode == IcsPlayingWhite)) ||
2402                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2403                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2404                             started = STARTED_CHATTER; // own kibitz we simply discard
2405                         else {
2406                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2407                             parse_pos = 0; parse[0] = NULLCHAR;
2408                             savingComment = TRUE;
2409                             suppressKibitz = gameMode != IcsObserving ? 2 :
2410                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2411                         } 
2412                         continue;
2413                 } else
2414                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2415                     started = STARTED_CHATTER;
2416                     suppressKibitz = TRUE;
2417                 }
2418             } // [HGM] kibitz: end of patch
2419
2420 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2421
2422             // [HGM] chat: intercept tells by users for which we have an open chat window
2423             channel = -1;
2424             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2425                                            looking_at(buf, &i, "* whispers:") ||
2426                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2427                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2428                 int p;
2429                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2430                 chattingPartner = -1;
2431
2432                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2433                 for(p=0; p<MAX_CHAT; p++) {
2434                     if(channel == atoi(chatPartner[p])) {
2435                     talker[0] = '['; strcat(talker, "]");
2436                     chattingPartner = p; break;
2437                     }
2438                 } else
2439                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2440                 for(p=0; p<MAX_CHAT; p++) {
2441                     if(!strcmp("WHISPER", chatPartner[p])) {
2442                         talker[0] = '['; strcat(talker, "]");
2443                         chattingPartner = p; break;
2444                     }
2445                 }
2446                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2447                 for(p=0; p<MAX_CHAT; p++) if(!strcasecmp(talker+1, chatPartner[p])) {
2448                     talker[0] = 0;
2449                     chattingPartner = p; break;
2450                 }
2451                 if(chattingPartner<0) i = oldi; else {
2452                     started = STARTED_COMMENT;
2453                     parse_pos = 0; parse[0] = NULLCHAR;
2454                     savingComment = TRUE;
2455                     suppressKibitz = TRUE;
2456                 }
2457             } // [HGM] chat: end of patch
2458
2459             if (appData.zippyTalk || appData.zippyPlay) {
2460                 /* [DM] Backup address for color zippy lines */
2461                 backup = i;
2462 #if ZIPPY
2463        #ifdef WIN32
2464                if (loggedOn == TRUE)
2465                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2466                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2467        #else
2468                 if (ZippyControl(buf, &i) ||
2469                     ZippyConverse(buf, &i) ||
2470                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2471                       loggedOn = TRUE;
2472                       if (!appData.colorize) continue;
2473                 }
2474        #endif
2475 #endif
2476             } // [DM] 'else { ' deleted
2477                 if (
2478                     /* Regular tells and says */
2479                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2480                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2481                     looking_at(buf, &i, "* says: ") ||
2482                     /* Don't color "message" or "messages" output */
2483                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2484                     looking_at(buf, &i, "*. * at *:*: ") ||
2485                     looking_at(buf, &i, "--* (*:*): ") ||
2486                     /* Message notifications (same color as tells) */
2487                     looking_at(buf, &i, "* has left a message ") ||
2488                     looking_at(buf, &i, "* just sent you a message:\n") ||
2489                     /* Whispers and kibitzes */
2490                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2491                     looking_at(buf, &i, "* kibitzes: ") ||
2492                     /* Channel tells */
2493                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2494
2495                   if (tkind == 1 && strchr(star_match[0], ':')) {
2496                       /* Avoid "tells you:" spoofs in channels */
2497                      tkind = 3;
2498                   }
2499                   if (star_match[0][0] == NULLCHAR ||
2500                       strchr(star_match[0], ' ') ||
2501                       (tkind == 3 && strchr(star_match[1], ' '))) {
2502                     /* Reject bogus matches */
2503                     i = oldi;
2504                   } else {
2505                     if (appData.colorize) {
2506                       if (oldi > next_out) {
2507                         SendToPlayer(&buf[next_out], oldi - next_out);
2508                         next_out = oldi;
2509                       }
2510                       switch (tkind) {
2511                       case 1:
2512                         Colorize(ColorTell, FALSE);
2513                         curColor = ColorTell;
2514                         break;
2515                       case 2:
2516                         Colorize(ColorKibitz, FALSE);
2517                         curColor = ColorKibitz;
2518                         break;
2519                       case 3:
2520                         p = strrchr(star_match[1], '(');
2521                         if (p == NULL) {
2522                           p = star_match[1];
2523                         } else {
2524                           p++;
2525                         }
2526                         if (atoi(p) == 1) {
2527                           Colorize(ColorChannel1, FALSE);
2528                           curColor = ColorChannel1;
2529                         } else {
2530                           Colorize(ColorChannel, FALSE);
2531                           curColor = ColorChannel;
2532                         }
2533                         break;
2534                       case 5:
2535                         curColor = ColorNormal;
2536                         break;
2537                       }
2538                     }
2539                     if (started == STARTED_NONE && appData.autoComment &&
2540                         (gameMode == IcsObserving ||
2541                          gameMode == IcsPlayingWhite ||
2542                          gameMode == IcsPlayingBlack)) {
2543                       parse_pos = i - oldi;
2544                       memcpy(parse, &buf[oldi], parse_pos);
2545                       parse[parse_pos] = NULLCHAR;
2546                       started = STARTED_COMMENT;
2547                       savingComment = TRUE;
2548                     } else {
2549                       started = STARTED_CHATTER;
2550                       savingComment = FALSE;
2551                     }
2552                     loggedOn = TRUE;
2553                     continue;
2554                   }
2555                 }
2556
2557                 if (looking_at(buf, &i, "* s-shouts: ") ||
2558                     looking_at(buf, &i, "* c-shouts: ")) {
2559                     if (appData.colorize) {
2560                         if (oldi > next_out) {
2561                             SendToPlayer(&buf[next_out], oldi - next_out);
2562                             next_out = oldi;
2563                         }
2564                         Colorize(ColorSShout, FALSE);
2565                         curColor = ColorSShout;
2566                     }
2567                     loggedOn = TRUE;
2568                     started = STARTED_CHATTER;
2569                     continue;
2570                 }
2571
2572                 if (looking_at(buf, &i, "--->")) {
2573                     loggedOn = TRUE;
2574                     continue;
2575                 }
2576
2577                 if (looking_at(buf, &i, "* shouts: ") ||
2578                     looking_at(buf, &i, "--> ")) {
2579                     if (appData.colorize) {
2580                         if (oldi > next_out) {
2581                             SendToPlayer(&buf[next_out], oldi - next_out);
2582                             next_out = oldi;
2583                         }
2584                         Colorize(ColorShout, FALSE);
2585                         curColor = ColorShout;
2586                     }
2587                     loggedOn = TRUE;
2588                     started = STARTED_CHATTER;
2589                     continue;
2590                 }
2591
2592                 if (looking_at( buf, &i, "Challenge:")) {
2593                     if (appData.colorize) {
2594                         if (oldi > next_out) {
2595                             SendToPlayer(&buf[next_out], oldi - next_out);
2596                             next_out = oldi;
2597                         }
2598                         Colorize(ColorChallenge, FALSE);
2599                         curColor = ColorChallenge;
2600                     }
2601                     loggedOn = TRUE;
2602                     continue;
2603                 }
2604
2605                 if (looking_at(buf, &i, "* offers you") ||
2606                     looking_at(buf, &i, "* offers to be") ||
2607                     looking_at(buf, &i, "* would like to") ||
2608                     looking_at(buf, &i, "* requests to") ||
2609                     looking_at(buf, &i, "Your opponent offers") ||
2610                     looking_at(buf, &i, "Your opponent requests")) {
2611
2612                     if (appData.colorize) {
2613                         if (oldi > next_out) {
2614                             SendToPlayer(&buf[next_out], oldi - next_out);
2615                             next_out = oldi;
2616                         }
2617                         Colorize(ColorRequest, FALSE);
2618                         curColor = ColorRequest;
2619                     }
2620                     continue;
2621                 }
2622
2623                 if (looking_at(buf, &i, "* (*) seeking")) {
2624                     if (appData.colorize) {
2625                         if (oldi > next_out) {
2626                             SendToPlayer(&buf[next_out], oldi - next_out);
2627                             next_out = oldi;
2628                         }
2629                         Colorize(ColorSeek, FALSE);
2630                         curColor = ColorSeek;
2631                     }
2632                     continue;
2633             }
2634
2635             if (looking_at(buf, &i, "\\   ")) {
2636                 if (prevColor != ColorNormal) {
2637                     if (oldi > next_out) {
2638                         SendToPlayer(&buf[next_out], oldi - next_out);
2639                         next_out = oldi;
2640                     }
2641                     Colorize(prevColor, TRUE);
2642                     curColor = prevColor;
2643                 }
2644                 if (savingComment) {
2645                     parse_pos = i - oldi;
2646                     memcpy(parse, &buf[oldi], parse_pos);
2647                     parse[parse_pos] = NULLCHAR;
2648                     started = STARTED_COMMENT;
2649                 } else {
2650                     started = STARTED_CHATTER;
2651                 }
2652                 continue;
2653             }
2654
2655             if (looking_at(buf, &i, "Black Strength :") ||
2656                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2657                 looking_at(buf, &i, "<10>") ||
2658                 looking_at(buf, &i, "#@#")) {
2659                 /* Wrong board style */
2660                 loggedOn = TRUE;
2661                 SendToICS(ics_prefix);
2662                 SendToICS("set style 12\n");
2663                 SendToICS(ics_prefix);
2664                 SendToICS("refresh\n");
2665                 continue;
2666             }
2667             
2668             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2669                 ICSInitScript();
2670                 have_sent_ICS_logon = 1;
2671                 continue;
2672             }
2673               
2674             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2675                 (looking_at(buf, &i, "\n<12> ") ||
2676                  looking_at(buf, &i, "<12> "))) {
2677                 loggedOn = TRUE;
2678                 if (oldi > next_out) {
2679                     SendToPlayer(&buf[next_out], oldi - next_out);
2680                 }
2681                 next_out = i;
2682                 started = STARTED_BOARD;
2683                 parse_pos = 0;
2684                 continue;
2685             }
2686
2687             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2688                 looking_at(buf, &i, "<b1> ")) {
2689                 if (oldi > next_out) {
2690                     SendToPlayer(&buf[next_out], oldi - next_out);
2691                 }
2692                 next_out = i;
2693                 started = STARTED_HOLDINGS;
2694                 parse_pos = 0;
2695                 continue;
2696             }
2697
2698             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2699                 loggedOn = TRUE;
2700                 /* Header for a move list -- first line */
2701
2702                 switch (ics_getting_history) {
2703                   case H_FALSE:
2704                     switch (gameMode) {
2705                       case IcsIdle:
2706                       case BeginningOfGame:
2707                         /* User typed "moves" or "oldmoves" while we
2708                            were idle.  Pretend we asked for these
2709                            moves and soak them up so user can step
2710                            through them and/or save them.
2711                            */
2712                         Reset(FALSE, TRUE);
2713                         gameMode = IcsObserving;
2714                         ModeHighlight();
2715                         ics_gamenum = -1;
2716                         ics_getting_history = H_GOT_UNREQ_HEADER;
2717                         break;
2718                       case EditGame: /*?*/
2719                       case EditPosition: /*?*/
2720                         /* Should above feature work in these modes too? */
2721                         /* For now it doesn't */
2722                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2723                         break;
2724                       default:
2725                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2726                         break;
2727                     }
2728                     break;
2729                   case H_REQUESTED:
2730                     /* Is this the right one? */
2731                     if (gameInfo.white && gameInfo.black &&
2732                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2733                         strcmp(gameInfo.black, star_match[2]) == 0) {
2734                         /* All is well */
2735                         ics_getting_history = H_GOT_REQ_HEADER;
2736                     }
2737                     break;
2738                   case H_GOT_REQ_HEADER:
2739                   case H_GOT_UNREQ_HEADER:
2740                   case H_GOT_UNWANTED_HEADER:
2741                   case H_GETTING_MOVES:
2742                     /* Should not happen */
2743                     DisplayError(_("Error gathering move list: two headers"), 0);
2744                     ics_getting_history = H_FALSE;
2745                     break;
2746                 }
2747
2748                 /* Save player ratings into gameInfo if needed */
2749                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2750                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2751                     (gameInfo.whiteRating == -1 ||
2752                      gameInfo.blackRating == -1)) {
2753
2754                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2755                     gameInfo.blackRating = string_to_rating(star_match[3]);
2756                     if (appData.debugMode)
2757                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2758                               gameInfo.whiteRating, gameInfo.blackRating);
2759                 }
2760                 continue;
2761             }
2762
2763             if (looking_at(buf, &i,
2764               "* * match, initial time: * minute*, increment: * second")) {
2765                 /* Header for a move list -- second line */
2766                 /* Initial board will follow if this is a wild game */
2767                 if (gameInfo.event != NULL) free(gameInfo.event);
2768                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2769                 gameInfo.event = StrSave(str);
2770                 /* [HGM] we switched variant. Translate boards if needed. */
2771                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2772                 continue;
2773             }
2774
2775             if (looking_at(buf, &i, "Move  ")) {
2776                 /* Beginning of a move list */
2777                 switch (ics_getting_history) {
2778                   case H_FALSE:
2779                     /* Normally should not happen */
2780                     /* Maybe user hit reset while we were parsing */
2781                     break;
2782                   case H_REQUESTED:
2783                     /* Happens if we are ignoring a move list that is not
2784                      * the one we just requested.  Common if the user
2785                      * tries to observe two games without turning off
2786                      * getMoveList */
2787                     break;
2788                   case H_GETTING_MOVES:
2789                     /* Should not happen */
2790                     DisplayError(_("Error gathering move list: nested"), 0);
2791                     ics_getting_history = H_FALSE;
2792                     break;
2793                   case H_GOT_REQ_HEADER:
2794                     ics_getting_history = H_GETTING_MOVES;
2795                     started = STARTED_MOVES;
2796                     parse_pos = 0;
2797                     if (oldi > next_out) {
2798                         SendToPlayer(&buf[next_out], oldi - next_out);
2799                     }
2800                     break;
2801                   case H_GOT_UNREQ_HEADER:
2802                     ics_getting_history = H_GETTING_MOVES;
2803                     started = STARTED_MOVES_NOHIDE;
2804                     parse_pos = 0;
2805                     break;
2806                   case H_GOT_UNWANTED_HEADER:
2807                     ics_getting_history = H_FALSE;
2808                     break;
2809                 }
2810                 continue;
2811             }                           
2812             
2813             if (looking_at(buf, &i, "% ") ||
2814                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2815                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2816                 savingComment = FALSE;
2817                 switch (started) {
2818                   case STARTED_MOVES:
2819                   case STARTED_MOVES_NOHIDE:
2820                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2821                     parse[parse_pos + i - oldi] = NULLCHAR;
2822                     ParseGameHistory(parse);
2823 #if ZIPPY
2824                     if (appData.zippyPlay && first.initDone) {
2825                         FeedMovesToProgram(&first, forwardMostMove);
2826                         if (gameMode == IcsPlayingWhite) {
2827                             if (WhiteOnMove(forwardMostMove)) {
2828                                 if (first.sendTime) {
2829                                   if (first.useColors) {
2830                                     SendToProgram("black\n", &first); 
2831                                   }
2832                                   SendTimeRemaining(&first, TRUE);
2833                                 }
2834 #if 0
2835                                 if (first.useColors) {
2836                                   SendToProgram("white\ngo\n", &first);
2837                                 } else {
2838                                   SendToProgram("go\n", &first);
2839                                 }
2840 #else
2841                                 if (first.useColors) {
2842                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2843                                 }
2844                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2845 #endif
2846                                 first.maybeThinking = TRUE;
2847                             } else {
2848                                 if (first.usePlayother) {
2849                                   if (first.sendTime) {
2850                                     SendTimeRemaining(&first, TRUE);
2851                                   }
2852                                   SendToProgram("playother\n", &first);
2853                                   firstMove = FALSE;
2854                                 } else {
2855                                   firstMove = TRUE;
2856                                 }
2857                             }
2858                         } else if (gameMode == IcsPlayingBlack) {
2859                             if (!WhiteOnMove(forwardMostMove)) {
2860                                 if (first.sendTime) {
2861                                   if (first.useColors) {
2862                                     SendToProgram("white\n", &first);
2863                                   }
2864                                   SendTimeRemaining(&first, FALSE);
2865                                 }
2866 #if 0
2867                                 if (first.useColors) {
2868                                   SendToProgram("black\ngo\n", &first);
2869                                 } else {
2870                                   SendToProgram("go\n", &first);
2871                                 }
2872 #else
2873                                 if (first.useColors) {
2874                                   SendToProgram("black\n", &first);
2875                                 }
2876                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2877 #endif
2878                                 first.maybeThinking = TRUE;
2879                             } else {
2880                                 if (first.usePlayother) {
2881                                   if (first.sendTime) {
2882                                     SendTimeRemaining(&first, FALSE);
2883                                   }
2884                                   SendToProgram("playother\n", &first);
2885                                   firstMove = FALSE;
2886                                 } else {
2887                                   firstMove = TRUE;
2888                                 }
2889                             }
2890                         }                       
2891                     }
2892 #endif
2893                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2894                         /* Moves came from oldmoves or moves command
2895                            while we weren't doing anything else.
2896                            */
2897                         currentMove = forwardMostMove;
2898                         ClearHighlights();/*!!could figure this out*/
2899                         flipView = appData.flipView;
2900                         DrawPosition(FALSE, boards[currentMove]);
2901                         DisplayBothClocks();
2902                         sprintf(str, "%s vs. %s",
2903                                 gameInfo.white, gameInfo.black);
2904                         DisplayTitle(str);
2905                         gameMode = IcsIdle;
2906                     } else {
2907                         /* Moves were history of an active game */
2908                         if (gameInfo.resultDetails != NULL) {
2909                             free(gameInfo.resultDetails);
2910                             gameInfo.resultDetails = NULL;
2911                         }
2912                     }
2913                     HistorySet(parseList, backwardMostMove,
2914                                forwardMostMove, currentMove-1);
2915                     DisplayMove(currentMove - 1);
2916                     if (started == STARTED_MOVES) next_out = i;
2917                     started = STARTED_NONE;
2918                     ics_getting_history = H_FALSE;
2919                     break;
2920
2921                   case STARTED_OBSERVE:
2922                     started = STARTED_NONE;
2923                     SendToICS(ics_prefix);
2924                     SendToICS("refresh\n");
2925                     break;
2926
2927                   default:
2928                     break;
2929                 }
2930                 if(bookHit) { // [HGM] book: simulate book reply
2931                     static char bookMove[MSG_SIZ]; // a bit generous?
2932
2933                     programStats.nodes = programStats.depth = programStats.time = 
2934                     programStats.score = programStats.got_only_move = 0;
2935                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2936
2937                     strcpy(bookMove, "move ");
2938                     strcat(bookMove, bookHit);
2939                     HandleMachineMove(bookMove, &first);
2940                 }
2941                 continue;
2942             }
2943             
2944             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2945                  started == STARTED_HOLDINGS ||
2946                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2947                 /* Accumulate characters in move list or board */
2948                 parse[parse_pos++] = buf[i];
2949             }
2950             
2951             /* Start of game messages.  Mostly we detect start of game
2952                when the first board image arrives.  On some versions
2953                of the ICS, though, we need to do a "refresh" after starting
2954                to observe in order to get the current board right away. */
2955             if (looking_at(buf, &i, "Adding game * to observation list")) {
2956                 started = STARTED_OBSERVE;
2957                 continue;
2958             }
2959
2960             /* Handle auto-observe */
2961             if (appData.autoObserve &&
2962                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2963                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2964                 char *player;
2965                 /* Choose the player that was highlighted, if any. */
2966                 if (star_match[0][0] == '\033' ||
2967                     star_match[1][0] != '\033') {
2968                     player = star_match[0];
2969                 } else {
2970                     player = star_match[2];
2971                 }
2972                 sprintf(str, "%sobserve %s\n",
2973                         ics_prefix, StripHighlightAndTitle(player));
2974                 SendToICS(str);
2975
2976                 /* Save ratings from notify string */
2977                 strcpy(player1Name, star_match[0]);
2978                 player1Rating = string_to_rating(star_match[1]);
2979                 strcpy(player2Name, star_match[2]);
2980                 player2Rating = string_to_rating(star_match[3]);
2981
2982                 if (appData.debugMode)
2983                   fprintf(debugFP, 
2984                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2985                           player1Name, player1Rating,
2986                           player2Name, player2Rating);
2987
2988                 continue;
2989             }
2990
2991             /* Deal with automatic examine mode after a game,
2992                and with IcsObserving -> IcsExamining transition */
2993             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2994                 looking_at(buf, &i, "has made you an examiner of game *")) {
2995
2996                 int gamenum = atoi(star_match[0]);
2997                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2998                     gamenum == ics_gamenum) {
2999                     /* We were already playing or observing this game;
3000                        no need to refetch history */
3001                     gameMode = IcsExamining;
3002                     if (pausing) {
3003                         pauseExamForwardMostMove = forwardMostMove;
3004                     } else if (currentMove < forwardMostMove) {
3005                         ForwardInner(forwardMostMove);
3006                     }
3007                 } else {
3008                     /* I don't think this case really can happen */
3009                     SendToICS(ics_prefix);
3010                     SendToICS("refresh\n");
3011                 }
3012                 continue;
3013             }    
3014             
3015             /* Error messages */
3016 //          if (ics_user_moved) {
3017             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3018                 if (looking_at(buf, &i, "Illegal move") ||
3019                     looking_at(buf, &i, "Not a legal move") ||
3020                     looking_at(buf, &i, "Your king is in check") ||
3021                     looking_at(buf, &i, "It isn't your turn") ||
3022                     looking_at(buf, &i, "It is not your move")) {
3023                     /* Illegal move */
3024                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3025                         currentMove = --forwardMostMove;
3026                         DisplayMove(currentMove - 1); /* before DMError */
3027                         DrawPosition(FALSE, boards[currentMove]);
3028                         SwitchClocks();
3029                         DisplayBothClocks();
3030                     }
3031                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3032                     ics_user_moved = 0;
3033                     continue;
3034                 }
3035             }
3036
3037             if (looking_at(buf, &i, "still have time") ||
3038                 looking_at(buf, &i, "not out of time") ||
3039                 looking_at(buf, &i, "either player is out of time") ||
3040                 looking_at(buf, &i, "has timeseal; checking")) {
3041                 /* We must have called his flag a little too soon */
3042                 whiteFlag = blackFlag = FALSE;
3043                 continue;
3044             }
3045
3046             if (looking_at(buf, &i, "added * seconds to") ||
3047                 looking_at(buf, &i, "seconds were added to")) {
3048                 /* Update the clocks */
3049                 SendToICS(ics_prefix);
3050                 SendToICS("refresh\n");
3051                 continue;
3052             }
3053
3054             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3055                 ics_clock_paused = TRUE;
3056                 StopClocks();
3057                 continue;
3058             }
3059
3060             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3061                 ics_clock_paused = FALSE;
3062                 StartClocks();
3063                 continue;
3064             }
3065
3066             /* Grab player ratings from the Creating: message.
3067                Note we have to check for the special case when
3068                the ICS inserts things like [white] or [black]. */
3069             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3070                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3071                 /* star_matches:
3072                    0    player 1 name (not necessarily white)
3073                    1    player 1 rating
3074                    2    empty, white, or black (IGNORED)
3075                    3    player 2 name (not necessarily black)
3076                    4    player 2 rating
3077                    
3078                    The names/ratings are sorted out when the game
3079                    actually starts (below).
3080                 */
3081                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3082                 player1Rating = string_to_rating(star_match[1]);
3083                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3084                 player2Rating = string_to_rating(star_match[4]);
3085
3086                 if (appData.debugMode)
3087                   fprintf(debugFP, 
3088                           "Ratings from 'Creating:' %s %d, %s %d\n",
3089                           player1Name, player1Rating,
3090                           player2Name, player2Rating);
3091
3092                 continue;
3093             }
3094             
3095             /* Improved generic start/end-of-game messages */
3096             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3097                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3098                 /* If tkind == 0: */
3099                 /* star_match[0] is the game number */
3100                 /*           [1] is the white player's name */
3101                 /*           [2] is the black player's name */
3102                 /* For end-of-game: */
3103                 /*           [3] is the reason for the game end */
3104                 /*           [4] is a PGN end game-token, preceded by " " */
3105                 /* For start-of-game: */
3106                 /*           [3] begins with "Creating" or "Continuing" */
3107                 /*           [4] is " *" or empty (don't care). */
3108                 int gamenum = atoi(star_match[0]);
3109                 char *whitename, *blackname, *why, *endtoken;
3110                 ChessMove endtype = (ChessMove) 0;
3111
3112                 if (tkind == 0) {
3113                   whitename = star_match[1];
3114                   blackname = star_match[2];
3115                   why = star_match[3];
3116                   endtoken = star_match[4];
3117                 } else {
3118                   whitename = star_match[1];
3119                   blackname = star_match[3];
3120                   why = star_match[5];
3121                   endtoken = star_match[6];
3122                 }
3123
3124                 /* Game start messages */
3125                 if (strncmp(why, "Creating ", 9) == 0 ||
3126                     strncmp(why, "Continuing ", 11) == 0) {
3127                     gs_gamenum = gamenum;
3128                     strcpy(gs_kind, strchr(why, ' ') + 1);
3129 #if ZIPPY
3130                     if (appData.zippyPlay) {
3131                         ZippyGameStart(whitename, blackname);
3132                     }
3133 #endif /*ZIPPY*/
3134                     continue;
3135                 }
3136
3137                 /* Game end messages */
3138                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3139                     ics_gamenum != gamenum) {
3140                     continue;
3141                 }
3142                 while (endtoken[0] == ' ') endtoken++;
3143                 switch (endtoken[0]) {
3144                   case '*':
3145                   default:
3146                     endtype = GameUnfinished;
3147                     break;
3148                   case '0':
3149                     endtype = BlackWins;
3150                     break;
3151                   case '1':
3152                     if (endtoken[1] == '/')
3153                       endtype = GameIsDrawn;
3154                     else
3155                       endtype = WhiteWins;
3156                     break;
3157                 }
3158                 GameEnds(endtype, why, GE_ICS);
3159 #if ZIPPY
3160                 if (appData.zippyPlay && first.initDone) {
3161                     ZippyGameEnd(endtype, why);
3162                     if (first.pr == NULL) {
3163                       /* Start the next process early so that we'll
3164                          be ready for the next challenge */
3165                       StartChessProgram(&first);
3166                     }
3167                     /* Send "new" early, in case this command takes
3168                        a long time to finish, so that we'll be ready
3169                        for the next challenge. */
3170                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3171                     Reset(TRUE, TRUE);
3172                 }
3173 #endif /*ZIPPY*/
3174                 continue;
3175             }
3176
3177             if (looking_at(buf, &i, "Removing game * from observation") ||
3178                 looking_at(buf, &i, "no longer observing game *") ||
3179                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3180                 if (gameMode == IcsObserving &&
3181                     atoi(star_match[0]) == ics_gamenum)
3182                   {
3183                       /* icsEngineAnalyze */
3184                       if (appData.icsEngineAnalyze) {
3185                             ExitAnalyzeMode();
3186                             ModeHighlight();
3187                       }
3188                       StopClocks();
3189                       gameMode = IcsIdle;
3190                       ics_gamenum = -1;
3191                       ics_user_moved = FALSE;
3192                   }
3193                 continue;
3194             }
3195
3196             if (looking_at(buf, &i, "no longer examining game *")) {
3197                 if (gameMode == IcsExamining &&
3198                     atoi(star_match[0]) == ics_gamenum)
3199                   {
3200                       gameMode = IcsIdle;
3201                       ics_gamenum = -1;
3202                       ics_user_moved = FALSE;
3203                   }
3204                 continue;
3205             }
3206
3207             /* Advance leftover_start past any newlines we find,
3208                so only partial lines can get reparsed */
3209             if (looking_at(buf, &i, "\n")) {
3210                 prevColor = curColor;
3211                 if (curColor != ColorNormal) {
3212                     if (oldi > next_out) {
3213                         SendToPlayer(&buf[next_out], oldi - next_out);
3214                         next_out = oldi;
3215                     }
3216                     Colorize(ColorNormal, FALSE);
3217                     curColor = ColorNormal;
3218                 }
3219                 if (started == STARTED_BOARD) {
3220                     started = STARTED_NONE;
3221                     parse[parse_pos] = NULLCHAR;
3222                     ParseBoard12(parse);
3223                     ics_user_moved = 0;
3224
3225                     /* Send premove here */
3226                     if (appData.premove) {
3227                       char str[MSG_SIZ];
3228                       if (currentMove == 0 &&
3229                           gameMode == IcsPlayingWhite &&
3230                           appData.premoveWhite) {
3231                         sprintf(str, "%s%s\n", ics_prefix,
3232                                 appData.premoveWhiteText);
3233                         if (appData.debugMode)
3234                           fprintf(debugFP, "Sending premove:\n");
3235                         SendToICS(str);
3236                       } else if (currentMove == 1 &&
3237                                  gameMode == IcsPlayingBlack &&
3238                                  appData.premoveBlack) {
3239                         sprintf(str, "%s%s\n", ics_prefix,
3240                                 appData.premoveBlackText);
3241                         if (appData.debugMode)
3242                           fprintf(debugFP, "Sending premove:\n");
3243                         SendToICS(str);
3244                       } else if (gotPremove) {
3245                         gotPremove = 0;
3246                         ClearPremoveHighlights();
3247                         if (appData.debugMode)
3248                           fprintf(debugFP, "Sending premove:\n");
3249                           UserMoveEvent(premoveFromX, premoveFromY, 
3250                                         premoveToX, premoveToY, 
3251                                         premovePromoChar);
3252                       }
3253                     }
3254
3255                     /* Usually suppress following prompt */
3256                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3257                         if (looking_at(buf, &i, "*% ")) {
3258                             savingComment = FALSE;
3259                         }
3260                     }
3261                     next_out = i;
3262                 } else if (started == STARTED_HOLDINGS) {
3263                     int gamenum;
3264                     char new_piece[MSG_SIZ];
3265                     started = STARTED_NONE;
3266                     parse[parse_pos] = NULLCHAR;
3267                     if (appData.debugMode)
3268                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3269                                                         parse, currentMove);
3270                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3271                         gamenum == ics_gamenum) {
3272                         if (gameInfo.variant == VariantNormal) {
3273                           /* [HGM] We seem to switch variant during a game!
3274                            * Presumably no holdings were displayed, so we have
3275                            * to move the position two files to the right to
3276                            * create room for them!
3277                            */
3278                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3279                           /* Get a move list just to see the header, which
3280                              will tell us whether this is really bug or zh */
3281                           if (ics_getting_history == H_FALSE) {
3282                             ics_getting_history = H_REQUESTED;
3283                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3284                             SendToICS(str);
3285                           }
3286                         }
3287                         new_piece[0] = NULLCHAR;
3288                         sscanf(parse, "game %d white [%s black [%s <- %s",
3289                                &gamenum, white_holding, black_holding,
3290                                new_piece);
3291                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3292                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3293                         /* [HGM] copy holdings to board holdings area */
3294                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3295                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3296 #if ZIPPY
3297                         if (appData.zippyPlay && first.initDone) {
3298                             ZippyHoldings(white_holding, black_holding,
3299                                           new_piece);
3300                         }
3301 #endif /*ZIPPY*/
3302                         if (tinyLayout || smallLayout) {
3303                             char wh[16], bh[16];
3304                             PackHolding(wh, white_holding);
3305                             PackHolding(bh, black_holding);
3306                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3307                                     gameInfo.white, gameInfo.black);
3308                         } else {
3309                             sprintf(str, "%s [%s] vs. %s [%s]",
3310                                     gameInfo.white, white_holding,
3311                                     gameInfo.black, black_holding);
3312                         }
3313
3314                         DrawPosition(FALSE, boards[currentMove]);
3315                         DisplayTitle(str);
3316                     }
3317                     /* Suppress following prompt */
3318                     if (looking_at(buf, &i, "*% ")) {
3319                         savingComment = FALSE;
3320                     }
3321                     next_out = i;
3322                 }
3323                 continue;
3324             }
3325
3326             i++;                /* skip unparsed character and loop back */
3327         }
3328         
3329         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3330             started != STARTED_HOLDINGS && i > next_out) {
3331             SendToPlayer(&buf[next_out], i - next_out);
3332             next_out = i;
3333         }
3334         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3335         
3336         leftover_len = buf_len - leftover_start;
3337         /* if buffer ends with something we couldn't parse,
3338            reparse it after appending the next read */
3339         
3340     } else if (count == 0) {
3341         RemoveInputSource(isr);
3342         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3343     } else {
3344         DisplayFatalError(_("Error reading from ICS"), error, 1);
3345     }
3346 }
3347
3348
3349 /* Board style 12 looks like this:
3350    
3351    <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
3352    
3353  * The "<12> " is stripped before it gets to this routine.  The two
3354  * trailing 0's (flip state and clock ticking) are later addition, and
3355  * some chess servers may not have them, or may have only the first.
3356  * Additional trailing fields may be added in the future.  
3357  */
3358
3359 #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"
3360
3361 #define RELATION_OBSERVING_PLAYED    0
3362 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3363 #define RELATION_PLAYING_MYMOVE      1
3364 #define RELATION_PLAYING_NOTMYMOVE  -1
3365 #define RELATION_EXAMINING           2
3366 #define RELATION_ISOLATED_BOARD     -3
3367 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3368
3369 void
3370 ParseBoard12(string)
3371      char *string;
3372
3373     GameMode newGameMode;
3374     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3375     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3376     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3377     char to_play, board_chars[200];
3378     char move_str[500], str[500], elapsed_time[500];
3379     char black[32], white[32];
3380     Board board;
3381     int prevMove = currentMove;
3382     int ticking = 2;
3383     ChessMove moveType;
3384     int fromX, fromY, toX, toY;
3385     char promoChar;
3386     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3387     char *bookHit = NULL; // [HGM] book
3388
3389     fromX = fromY = toX = toY = -1;
3390     
3391     newGame = FALSE;
3392
3393     if (appData.debugMode)
3394       fprintf(debugFP, _("Parsing board: %s\n"), string);
3395
3396     move_str[0] = NULLCHAR;
3397     elapsed_time[0] = NULLCHAR;
3398     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3399         int  i = 0, j;
3400         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3401             if(string[i] == ' ') { ranks++; files = 0; }
3402             else files++;
3403             i++;
3404         }
3405         for(j = 0; j <i; j++) board_chars[j] = string[j];
3406         board_chars[i] = '\0';
3407         string += i + 1;
3408     }
3409     n = sscanf(string, PATTERN, &to_play, &double_push,
3410                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3411                &gamenum, white, black, &relation, &basetime, &increment,
3412                &white_stren, &black_stren, &white_time, &black_time,
3413                &moveNum, str, elapsed_time, move_str, &ics_flip,
3414                &ticking);
3415
3416     if (n < 21) {
3417         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3418         DisplayError(str, 0);
3419         return;
3420     }
3421
3422     /* Convert the move number to internal form */
3423     moveNum = (moveNum - 1) * 2;
3424     if (to_play == 'B') moveNum++;
3425     if (moveNum >= MAX_MOVES) {
3426       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3427                         0, 1);
3428       return;
3429     }
3430     
3431     switch (relation) {
3432       case RELATION_OBSERVING_PLAYED:
3433       case RELATION_OBSERVING_STATIC:
3434         if (gamenum == -1) {
3435             /* Old ICC buglet */
3436             relation = RELATION_OBSERVING_STATIC;
3437         }
3438         newGameMode = IcsObserving;
3439         break;
3440       case RELATION_PLAYING_MYMOVE:
3441       case RELATION_PLAYING_NOTMYMOVE:
3442         newGameMode =
3443           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3444             IcsPlayingWhite : IcsPlayingBlack;
3445         break;
3446       case RELATION_EXAMINING:
3447         newGameMode = IcsExamining;
3448         break;
3449       case RELATION_ISOLATED_BOARD:
3450       default:
3451         /* Just display this board.  If user was doing something else,
3452            we will forget about it until the next board comes. */ 
3453         newGameMode = IcsIdle;
3454         break;
3455       case RELATION_STARTING_POSITION:
3456         newGameMode = gameMode;
3457         break;
3458     }
3459     
3460     /* Modify behavior for initial board display on move listing
3461        of wild games.
3462        */
3463     switch (ics_getting_history) {
3464       case H_FALSE:
3465       case H_REQUESTED:
3466         break;
3467       case H_GOT_REQ_HEADER:
3468       case H_GOT_UNREQ_HEADER:
3469         /* This is the initial position of the current game */
3470         gamenum = ics_gamenum;
3471         moveNum = 0;            /* old ICS bug workaround */
3472         if (to_play == 'B') {
3473           startedFromSetupPosition = TRUE;
3474           blackPlaysFirst = TRUE;
3475           moveNum = 1;
3476           if (forwardMostMove == 0) forwardMostMove = 1;
3477           if (backwardMostMove == 0) backwardMostMove = 1;
3478           if (currentMove == 0) currentMove = 1;
3479         }
3480         newGameMode = gameMode;
3481         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3482         break;
3483       case H_GOT_UNWANTED_HEADER:
3484         /* This is an initial board that we don't want */
3485         return;
3486       case H_GETTING_MOVES:
3487         /* Should not happen */
3488         DisplayError(_("Error gathering move list: extra board"), 0);
3489         ics_getting_history = H_FALSE;
3490         return;
3491     }
3492     
3493     /* Take action if this is the first board of a new game, or of a
3494        different game than is currently being displayed.  */
3495     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3496         relation == RELATION_ISOLATED_BOARD) {
3497         
3498         /* Forget the old game and get the history (if any) of the new one */
3499         if (gameMode != BeginningOfGame) {
3500           Reset(FALSE, TRUE);
3501         }
3502         newGame = TRUE;
3503         if (appData.autoRaiseBoard) BoardToTop();
3504         prevMove = -3;
3505         if (gamenum == -1) {
3506             newGameMode = IcsIdle;
3507         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3508                    appData.getMoveList) {
3509             /* Need to get game history */
3510             ics_getting_history = H_REQUESTED;
3511             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3512             SendToICS(str);
3513         }
3514         
3515         /* Initially flip the board to have black on the bottom if playing
3516            black or if the ICS flip flag is set, but let the user change
3517            it with the Flip View button. */
3518         flipView = appData.autoFlipView ? 
3519           (newGameMode == IcsPlayingBlack) || ics_flip :
3520           appData.flipView;
3521         
3522         /* Done with values from previous mode; copy in new ones */
3523         gameMode = newGameMode;
3524         ModeHighlight();
3525         ics_gamenum = gamenum;
3526         if (gamenum == gs_gamenum) {
3527             int klen = strlen(gs_kind);
3528             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3529             sprintf(str, "ICS %s", gs_kind);
3530             gameInfo.event = StrSave(str);
3531         } else {
3532             gameInfo.event = StrSave("ICS game");
3533         }
3534         gameInfo.site = StrSave(appData.icsHost);
3535         gameInfo.date = PGNDate();
3536         gameInfo.round = StrSave("-");
3537         gameInfo.white = StrSave(white);
3538         gameInfo.black = StrSave(black);
3539         timeControl = basetime * 60 * 1000;
3540         timeControl_2 = 0;
3541         timeIncrement = increment * 1000;
3542         movesPerSession = 0;
3543         gameInfo.timeControl = TimeControlTagValue();
3544         VariantSwitch(board, StringToVariant(gameInfo.event) );
3545   if (appData.debugMode) {
3546     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3547     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3548     setbuf(debugFP, NULL);
3549   }
3550
3551         gameInfo.outOfBook = NULL;
3552         
3553         /* Do we have the ratings? */
3554         if (strcmp(player1Name, white) == 0 &&
3555             strcmp(player2Name, black) == 0) {
3556             if (appData.debugMode)
3557               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3558                       player1Rating, player2Rating);
3559             gameInfo.whiteRating = player1Rating;
3560             gameInfo.blackRating = player2Rating;
3561         } else if (strcmp(player2Name, white) == 0 &&
3562                    strcmp(player1Name, black) == 0) {
3563             if (appData.debugMode)
3564               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3565                       player2Rating, player1Rating);
3566             gameInfo.whiteRating = player2Rating;
3567             gameInfo.blackRating = player1Rating;
3568         }
3569         player1Name[0] = player2Name[0] = NULLCHAR;
3570
3571         /* Silence shouts if requested */
3572         if (appData.quietPlay &&
3573             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3574             SendToICS(ics_prefix);
3575             SendToICS("set shout 0\n");
3576         }
3577     }
3578     
3579     /* Deal with midgame name changes */
3580     if (!newGame) {
3581         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3582             if (gameInfo.white) free(gameInfo.white);
3583             gameInfo.white = StrSave(white);
3584         }
3585         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3586             if (gameInfo.black) free(gameInfo.black);
3587             gameInfo.black = StrSave(black);
3588         }
3589     }
3590     
3591     /* Throw away game result if anything actually changes in examine mode */
3592     if (gameMode == IcsExamining && !newGame) {
3593         gameInfo.result = GameUnfinished;
3594         if (gameInfo.resultDetails != NULL) {
3595             free(gameInfo.resultDetails);
3596             gameInfo.resultDetails = NULL;
3597         }
3598     }
3599     
3600     /* In pausing && IcsExamining mode, we ignore boards coming
3601        in if they are in a different variation than we are. */
3602     if (pauseExamInvalid) return;
3603     if (pausing && gameMode == IcsExamining) {
3604         if (moveNum <= pauseExamForwardMostMove) {
3605             pauseExamInvalid = TRUE;
3606             forwardMostMove = pauseExamForwardMostMove;
3607             return;
3608         }
3609     }
3610     
3611   if (appData.debugMode) {
3612     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3613   }
3614     /* Parse the board */
3615     for (k = 0; k < ranks; k++) {
3616       for (j = 0; j < files; j++)
3617         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3618       if(gameInfo.holdingsWidth > 1) {
3619            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3620            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3621       }
3622     }
3623     CopyBoard(boards[moveNum], board);
3624     if (moveNum == 0) {
3625         startedFromSetupPosition =
3626           !CompareBoards(board, initialPosition);
3627         if(startedFromSetupPosition)
3628             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3629     }
3630
3631     /* [HGM] Set castling rights. Take the outermost Rooks,
3632        to make it also work for FRC opening positions. Note that board12
3633        is really defective for later FRC positions, as it has no way to
3634        indicate which Rook can castle if they are on the same side of King.
3635        For the initial position we grant rights to the outermost Rooks,
3636        and remember thos rights, and we then copy them on positions
3637        later in an FRC game. This means WB might not recognize castlings with
3638        Rooks that have moved back to their original position as illegal,
3639        but in ICS mode that is not its job anyway.
3640     */
3641     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3642     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3643
3644         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3645             if(board[0][i] == WhiteRook) j = i;
3646         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3647         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3648             if(board[0][i] == WhiteRook) j = i;
3649         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3650         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3651             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3652         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3653         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3654             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3655         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3656
3657         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3658         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3659             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3660         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3661             if(board[BOARD_HEIGHT-1][k] == bKing)
3662                 initialRights[5] = castlingRights[moveNum][5] = k;
3663     } else { int r;
3664         r = castlingRights[moveNum][0] = initialRights[0];
3665         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3666         r = castlingRights[moveNum][1] = initialRights[1];
3667         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3668         r = castlingRights[moveNum][3] = initialRights[3];
3669         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3670         r = castlingRights[moveNum][4] = initialRights[4];
3671         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3672         /* wildcastle kludge: always assume King has rights */
3673         r = castlingRights[moveNum][2] = initialRights[2];
3674         r = castlingRights[moveNum][5] = initialRights[5];
3675     }
3676     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3677     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3678
3679     
3680     if (ics_getting_history == H_GOT_REQ_HEADER ||
3681         ics_getting_history == H_GOT_UNREQ_HEADER) {
3682         /* This was an initial position from a move list, not
3683            the current position */
3684         return;
3685     }
3686     
3687     /* Update currentMove and known move number limits */
3688     newMove = newGame || moveNum > forwardMostMove;
3689
3690     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3691     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3692         takeback = forwardMostMove - moveNum;
3693         for (i = 0; i < takeback; i++) {
3694              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3695              SendToProgram("undo\n", &first);
3696         }
3697     }
3698
3699     if (newGame) {
3700         forwardMostMove = backwardMostMove = currentMove = moveNum;
3701         if (gameMode == IcsExamining && moveNum == 0) {
3702           /* Workaround for ICS limitation: we are not told the wild
3703              type when starting to examine a game.  But if we ask for
3704              the move list, the move list header will tell us */
3705             ics_getting_history = H_REQUESTED;
3706             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3707             SendToICS(str);
3708         }
3709     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3710                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3711         forwardMostMove = moveNum;
3712         if (!pausing || currentMove > forwardMostMove)
3713           currentMove = forwardMostMove;
3714     } else {
3715         /* New part of history that is not contiguous with old part */ 
3716         if (pausing && gameMode == IcsExamining) {
3717             pauseExamInvalid = TRUE;
3718             forwardMostMove = pauseExamForwardMostMove;
3719             return;
3720         }
3721         forwardMostMove = backwardMostMove = currentMove = moveNum;
3722         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3723             ics_getting_history = H_REQUESTED;
3724             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3725             SendToICS(str);
3726         }
3727     }
3728     
3729     /* Update the clocks */
3730     if (strchr(elapsed_time, '.')) {
3731       /* Time is in ms */
3732       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3733       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3734     } else {
3735       /* Time is in seconds */
3736       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3737       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3738     }
3739       
3740
3741 #if ZIPPY
3742     if (appData.zippyPlay && newGame &&
3743         gameMode != IcsObserving && gameMode != IcsIdle &&
3744         gameMode != IcsExamining)
3745       ZippyFirstBoard(moveNum, basetime, increment);
3746 #endif
3747     
3748     /* Put the move on the move list, first converting
3749        to canonical algebraic form. */
3750     if (moveNum > 0) {
3751   if (appData.debugMode) {
3752     if (appData.debugMode) { int f = forwardMostMove;
3753         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3754                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3755     }
3756     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3757     fprintf(debugFP, "moveNum = %d\n", moveNum);
3758     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3759     setbuf(debugFP, NULL);
3760   }
3761         if (moveNum <= backwardMostMove) {
3762             /* We don't know what the board looked like before
3763                this move.  Punt. */
3764             strcpy(parseList[moveNum - 1], move_str);
3765             strcat(parseList[moveNum - 1], " ");
3766             strcat(parseList[moveNum - 1], elapsed_time);
3767             moveList[moveNum - 1][0] = NULLCHAR;
3768         } else if (strcmp(move_str, "none") == 0) {
3769             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3770             /* Again, we don't know what the board looked like;
3771                this is really the start of the game. */
3772             parseList[moveNum - 1][0] = NULLCHAR;
3773             moveList[moveNum - 1][0] = NULLCHAR;
3774             backwardMostMove = moveNum;
3775             startedFromSetupPosition = TRUE;
3776             fromX = fromY = toX = toY = -1;
3777         } else {
3778           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3779           //                 So we parse the long-algebraic move string in stead of the SAN move
3780           int valid; char buf[MSG_SIZ], *prom;
3781
3782           // str looks something like "Q/a1-a2"; kill the slash
3783           if(str[1] == '/') 
3784                 sprintf(buf, "%c%s", str[0], str+2);
3785           else  strcpy(buf, str); // might be castling
3786           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3787                 strcat(buf, prom); // long move lacks promo specification!
3788           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3789                 if(appData.debugMode) 
3790                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3791                 strcpy(move_str, buf);
3792           }
3793           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3794                                 &fromX, &fromY, &toX, &toY, &promoChar)
3795                || ParseOneMove(buf, moveNum - 1, &moveType,
3796                                 &fromX, &fromY, &toX, &toY, &promoChar);
3797           // end of long SAN patch
3798           if (valid) {
3799             (void) CoordsToAlgebraic(boards[moveNum - 1],
3800                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3801                                      fromY, fromX, toY, toX, promoChar,
3802                                      parseList[moveNum-1]);
3803             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3804                              castlingRights[moveNum]) ) {
3805               case MT_NONE:
3806               case MT_STALEMATE:
3807               default:
3808                 break;
3809               case MT_CHECK:
3810                 if(gameInfo.variant != VariantShogi)
3811                     strcat(parseList[moveNum - 1], "+");
3812                 break;
3813               case MT_CHECKMATE:
3814               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3815                 strcat(parseList[moveNum - 1], "#");
3816                 break;
3817             }
3818             strcat(parseList[moveNum - 1], " ");
3819             strcat(parseList[moveNum - 1], elapsed_time);
3820             /* currentMoveString is set as a side-effect of ParseOneMove */
3821             strcpy(moveList[moveNum - 1], currentMoveString);
3822             strcat(moveList[moveNum - 1], "\n");
3823           } else {
3824             /* Move from ICS was illegal!?  Punt. */
3825   if (appData.debugMode) {
3826     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3827     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3828   }
3829 #if 0
3830             if (appData.testLegality && appData.debugMode) {
3831                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3832                 DisplayError(str, 0);
3833             }
3834 #endif
3835             strcpy(parseList[moveNum - 1], move_str);
3836             strcat(parseList[moveNum - 1], " ");
3837             strcat(parseList[moveNum - 1], elapsed_time);
3838             moveList[moveNum - 1][0] = NULLCHAR;
3839             fromX = fromY = toX = toY = -1;
3840           }
3841         }
3842   if (appData.debugMode) {
3843     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3844     setbuf(debugFP, NULL);
3845   }
3846
3847 #if ZIPPY
3848         /* Send move to chess program (BEFORE animating it). */
3849         if (appData.zippyPlay && !newGame && newMove && 
3850            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3851
3852             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3853                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3854                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3855                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3856                             move_str);
3857                     DisplayError(str, 0);
3858                 } else {
3859                     if (first.sendTime) {
3860                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3861                     }
3862                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3863                     if (firstMove && !bookHit) {
3864                         firstMove = FALSE;
3865                         if (first.useColors) {
3866                           SendToProgram(gameMode == IcsPlayingWhite ?
3867                                         "white\ngo\n" :
3868                                         "black\ngo\n", &first);
3869                         } else {
3870                           SendToProgram("go\n", &first);
3871                         }
3872                         first.maybeThinking = TRUE;
3873                     }
3874                 }
3875             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3876               if (moveList[moveNum - 1][0] == NULLCHAR) {
3877                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3878                 DisplayError(str, 0);
3879               } else {
3880                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3881                 SendMoveToProgram(moveNum - 1, &first);
3882               }
3883             }
3884         }
3885 #endif
3886     }
3887
3888     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3889         /* If move comes from a remote source, animate it.  If it
3890            isn't remote, it will have already been animated. */
3891         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3892             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3893         }
3894         if (!pausing && appData.highlightLastMove) {
3895             SetHighlights(fromX, fromY, toX, toY);
3896         }
3897     }
3898     
3899     /* Start the clocks */
3900     whiteFlag = blackFlag = FALSE;
3901     appData.clockMode = !(basetime == 0 && increment == 0);
3902     if (ticking == 0) {
3903       ics_clock_paused = TRUE;
3904       StopClocks();
3905     } else if (ticking == 1) {
3906       ics_clock_paused = FALSE;
3907     }
3908     if (gameMode == IcsIdle ||
3909         relation == RELATION_OBSERVING_STATIC ||
3910         relation == RELATION_EXAMINING ||
3911         ics_clock_paused)
3912       DisplayBothClocks();
3913     else
3914       StartClocks();
3915     
3916     /* Display opponents and material strengths */
3917     if (gameInfo.variant != VariantBughouse &&
3918         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3919         if (tinyLayout || smallLayout) {
3920             if(gameInfo.variant == VariantNormal)
3921                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3922                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3923                     basetime, increment);
3924             else
3925                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3926                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3927                     basetime, increment, (int) gameInfo.variant);
3928         } else {
3929             if(gameInfo.variant == VariantNormal)
3930                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3931                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3932                     basetime, increment);
3933             else
3934                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3935                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3936                     basetime, increment, VariantName(gameInfo.variant));
3937         }
3938         DisplayTitle(str);
3939   if (appData.debugMode) {
3940     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3941   }
3942     }
3943
3944    
3945     /* Display the board */
3946     if (!pausing && !appData.noGUI) {
3947       
3948       if (appData.premove)
3949           if (!gotPremove || 
3950              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3951              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3952               ClearPremoveHighlights();
3953
3954       DrawPosition(FALSE, boards[currentMove]);
3955       DisplayMove(moveNum - 1);
3956       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3957             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3958               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3959         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3960       }
3961     }
3962
3963     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3964 #if ZIPPY
3965     if(bookHit) { // [HGM] book: simulate book reply
3966         static char bookMove[MSG_SIZ]; // a bit generous?
3967
3968         programStats.nodes = programStats.depth = programStats.time = 
3969         programStats.score = programStats.got_only_move = 0;
3970         sprintf(programStats.movelist, "%s (xbook)", bookHit);
3971
3972         strcpy(bookMove, "move ");
3973         strcat(bookMove, bookHit);
3974         HandleMachineMove(bookMove, &first);
3975     }
3976 #endif
3977 }
3978
3979 void
3980 GetMoveListEvent()
3981 {
3982     char buf[MSG_SIZ];
3983     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3984         ics_getting_history = H_REQUESTED;
3985         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3986         SendToICS(buf);
3987     }
3988 }
3989
3990 void
3991 AnalysisPeriodicEvent(force)
3992      int force;
3993 {
3994     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3995          && !force) || !appData.periodicUpdates)
3996       return;
3997
3998     /* Send . command to Crafty to collect stats */
3999     SendToProgram(".\n", &first);
4000
4001     /* Don't send another until we get a response (this makes
4002        us stop sending to old Crafty's which don't understand
4003        the "." command (sending illegal cmds resets node count & time,
4004        which looks bad)) */
4005     programStats.ok_to_send = 0;
4006 }
4007
4008 void
4009 SendMoveToProgram(moveNum, cps)
4010      int moveNum;
4011      ChessProgramState *cps;
4012 {
4013     char buf[MSG_SIZ];
4014
4015     if (cps->useUsermove) {
4016       SendToProgram("usermove ", cps);
4017     }
4018     if (cps->useSAN) {
4019       char *space;
4020       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4021         int len = space - parseList[moveNum];
4022         memcpy(buf, parseList[moveNum], len);
4023         buf[len++] = '\n';
4024         buf[len] = NULLCHAR;
4025       } else {
4026         sprintf(buf, "%s\n", parseList[moveNum]);
4027       }
4028       SendToProgram(buf, cps);
4029     } else {
4030       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4031         AlphaRank(moveList[moveNum], 4);
4032         SendToProgram(moveList[moveNum], cps);
4033         AlphaRank(moveList[moveNum], 4); // and back
4034       } else
4035       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4036        * the engine. It would be nice to have a better way to identify castle 
4037        * moves here. */
4038       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4039                                                                          && cps->useOOCastle) {
4040         int fromX = moveList[moveNum][0] - AAA; 
4041         int fromY = moveList[moveNum][1] - ONE;
4042         int toX = moveList[moveNum][2] - AAA; 
4043         int toY = moveList[moveNum][3] - ONE;
4044         if((boards[moveNum][fromY][fromX] == WhiteKing 
4045             && boards[moveNum][toY][toX] == WhiteRook)
4046            || (boards[moveNum][fromY][fromX] == BlackKing 
4047                && boards[moveNum][toY][toX] == BlackRook)) {
4048           if(toX > fromX) SendToProgram("O-O\n", cps);
4049           else SendToProgram("O-O-O\n", cps);
4050         }
4051         else SendToProgram(moveList[moveNum], cps);
4052       }
4053       else SendToProgram(moveList[moveNum], cps);
4054       /* End of additions by Tord */
4055     }
4056
4057     /* [HGM] setting up the opening has brought engine in force mode! */
4058     /*       Send 'go' if we are in a mode where machine should play. */
4059     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4060         (gameMode == TwoMachinesPlay   ||
4061 #ifdef ZIPPY
4062          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4063 #endif
4064          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4065         SendToProgram("go\n", cps);
4066   if (appData.debugMode) {
4067     fprintf(debugFP, "(extra)\n");
4068   }
4069     }
4070     setboardSpoiledMachineBlack = 0;
4071 }
4072
4073 void
4074 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4075      ChessMove moveType;
4076      int fromX, fromY, toX, toY;
4077 {
4078     char user_move[MSG_SIZ];
4079
4080     switch (moveType) {
4081       default:
4082         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4083                 (int)moveType, fromX, fromY, toX, toY);
4084         DisplayError(user_move + strlen("say "), 0);
4085         break;
4086       case WhiteKingSideCastle:
4087       case BlackKingSideCastle:
4088       case WhiteQueenSideCastleWild:
4089       case BlackQueenSideCastleWild:
4090       /* PUSH Fabien */
4091       case WhiteHSideCastleFR:
4092       case BlackHSideCastleFR:
4093       /* POP Fabien */
4094         sprintf(user_move, "o-o\n");
4095         break;
4096       case WhiteQueenSideCastle:
4097       case BlackQueenSideCastle:
4098       case WhiteKingSideCastleWild:
4099       case BlackKingSideCastleWild:
4100       /* PUSH Fabien */
4101       case WhiteASideCastleFR:
4102       case BlackASideCastleFR:
4103       /* POP Fabien */
4104         sprintf(user_move, "o-o-o\n");
4105         break;
4106       case WhitePromotionQueen:
4107       case BlackPromotionQueen:
4108       case WhitePromotionRook:
4109       case BlackPromotionRook:
4110       case WhitePromotionBishop:
4111       case BlackPromotionBishop:
4112       case WhitePromotionKnight:
4113       case BlackPromotionKnight:
4114       case WhitePromotionKing:
4115       case BlackPromotionKing:
4116       case WhitePromotionChancellor:
4117       case BlackPromotionChancellor:
4118       case WhitePromotionArchbishop:
4119       case BlackPromotionArchbishop:
4120         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4121             sprintf(user_move, "%c%c%c%c=%c\n",
4122                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4123                 PieceToChar(WhiteFerz));
4124         else if(gameInfo.variant == VariantGreat)
4125             sprintf(user_move, "%c%c%c%c=%c\n",
4126                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4127                 PieceToChar(WhiteMan));
4128         else
4129             sprintf(user_move, "%c%c%c%c=%c\n",
4130                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4131                 PieceToChar(PromoPiece(moveType)));
4132         break;
4133       case WhiteDrop:
4134       case BlackDrop:
4135         sprintf(user_move, "%c@%c%c\n",
4136                 ToUpper(PieceToChar((ChessSquare) fromX)),
4137                 AAA + toX, ONE + toY);
4138         break;
4139       case NormalMove:
4140       case WhiteCapturesEnPassant:
4141       case BlackCapturesEnPassant:
4142       case IllegalMove:  /* could be a variant we don't quite understand */
4143         sprintf(user_move, "%c%c%c%c\n",
4144                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4145         break;
4146     }
4147     SendToICS(user_move);
4148     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4149         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4150 }
4151
4152 void
4153 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4154      int rf, ff, rt, ft;
4155      char promoChar;
4156      char move[7];
4157 {
4158     if (rf == DROP_RANK) {
4159         sprintf(move, "%c@%c%c\n",
4160                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4161     } else {
4162         if (promoChar == 'x' || promoChar == NULLCHAR) {
4163             sprintf(move, "%c%c%c%c\n",
4164                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4165         } else {
4166             sprintf(move, "%c%c%c%c%c\n",
4167                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4168         }
4169     }
4170 }
4171
4172 void
4173 ProcessICSInitScript(f)
4174      FILE *f;
4175 {
4176     char buf[MSG_SIZ];
4177
4178     while (fgets(buf, MSG_SIZ, f)) {
4179         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4180     }
4181
4182     fclose(f);
4183 }
4184
4185
4186 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4187 void
4188 AlphaRank(char *move, int n)
4189 {
4190 //    char *p = move, c; int x, y;
4191
4192     if (appData.debugMode) {
4193         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4194     }
4195
4196     if(move[1]=='*' && 
4197        move[2]>='0' && move[2]<='9' &&
4198        move[3]>='a' && move[3]<='x'    ) {
4199         move[1] = '@';
4200         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4201         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4202     } else
4203     if(move[0]>='0' && move[0]<='9' &&
4204        move[1]>='a' && move[1]<='x' &&
4205        move[2]>='0' && move[2]<='9' &&
4206        move[3]>='a' && move[3]<='x'    ) {
4207         /* input move, Shogi -> normal */
4208         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4209         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4210         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4211         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4212     } else
4213     if(move[1]=='@' &&
4214        move[3]>='0' && move[3]<='9' &&
4215        move[2]>='a' && move[2]<='x'    ) {
4216         move[1] = '*';
4217         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4218         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4219     } else
4220     if(
4221        move[0]>='a' && move[0]<='x' &&
4222        move[3]>='0' && move[3]<='9' &&
4223        move[2]>='a' && move[2]<='x'    ) {
4224          /* output move, normal -> Shogi */
4225         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4226         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4227         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4228         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4229         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4230     }
4231     if (appData.debugMode) {
4232         fprintf(debugFP, "   out = '%s'\n", move);
4233     }
4234 }
4235
4236 /* Parser for moves from gnuchess, ICS, or user typein box */
4237 Boolean
4238 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4239      char *move;
4240      int moveNum;
4241      ChessMove *moveType;
4242      int *fromX, *fromY, *toX, *toY;
4243      char *promoChar;
4244 {       
4245     if (appData.debugMode) {
4246         fprintf(debugFP, "move to parse: %s\n", move);
4247     }
4248     *moveType = yylexstr(moveNum, move);
4249
4250     switch (*moveType) {
4251       case WhitePromotionChancellor:
4252       case BlackPromotionChancellor:
4253       case WhitePromotionArchbishop:
4254       case BlackPromotionArchbishop:
4255       case WhitePromotionQueen:
4256       case BlackPromotionQueen:
4257       case WhitePromotionRook:
4258       case BlackPromotionRook:
4259       case WhitePromotionBishop:
4260       case BlackPromotionBishop:
4261       case WhitePromotionKnight:
4262       case BlackPromotionKnight:
4263       case WhitePromotionKing:
4264       case BlackPromotionKing:
4265       case NormalMove:
4266       case WhiteCapturesEnPassant:
4267       case BlackCapturesEnPassant:
4268       case WhiteKingSideCastle:
4269       case WhiteQueenSideCastle:
4270       case BlackKingSideCastle:
4271       case BlackQueenSideCastle:
4272       case WhiteKingSideCastleWild:
4273       case WhiteQueenSideCastleWild:
4274       case BlackKingSideCastleWild:
4275       case BlackQueenSideCastleWild:
4276       /* Code added by Tord: */
4277       case WhiteHSideCastleFR:
4278       case WhiteASideCastleFR:
4279       case BlackHSideCastleFR:
4280       case BlackASideCastleFR:
4281       /* End of code added by Tord */
4282       case IllegalMove:         /* bug or odd chess variant */
4283         *fromX = currentMoveString[0] - AAA;
4284         *fromY = currentMoveString[1] - ONE;
4285         *toX = currentMoveString[2] - AAA;
4286         *toY = currentMoveString[3] - ONE;
4287         *promoChar = currentMoveString[4];
4288         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4289             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4290     if (appData.debugMode) {
4291         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4292     }
4293             *fromX = *fromY = *toX = *toY = 0;
4294             return FALSE;
4295         }
4296         if (appData.testLegality) {
4297           return (*moveType != IllegalMove);
4298         } else {
4299           return !(fromX == fromY && toX == toY);
4300         }
4301
4302       case WhiteDrop:
4303       case BlackDrop:
4304         *fromX = *moveType == WhiteDrop ?
4305           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4306           (int) CharToPiece(ToLower(currentMoveString[0]));
4307         *fromY = DROP_RANK;
4308         *toX = currentMoveString[2] - AAA;
4309         *toY = currentMoveString[3] - ONE;
4310         *promoChar = NULLCHAR;
4311         return TRUE;
4312
4313       case AmbiguousMove:
4314       case ImpossibleMove:
4315       case (ChessMove) 0:       /* end of file */
4316       case ElapsedTime:
4317       case Comment:
4318       case PGNTag:
4319       case NAG:
4320       case WhiteWins:
4321       case BlackWins:
4322       case GameIsDrawn:
4323       default:
4324     if (appData.debugMode) {
4325         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4326     }
4327         /* bug? */
4328         *fromX = *fromY = *toX = *toY = 0;
4329         *promoChar = NULLCHAR;
4330         return FALSE;
4331     }
4332 }
4333
4334 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4335 // All positions will have equal probability, but the current method will not provide a unique
4336 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4337 #define DARK 1
4338 #define LITE 2
4339 #define ANY 3
4340
4341 int squaresLeft[4];
4342 int piecesLeft[(int)BlackPawn];
4343 int seed, nrOfShuffles;
4344
4345 void GetPositionNumber()
4346 {       // sets global variable seed
4347         int i;
4348
4349         seed = appData.defaultFrcPosition;
4350         if(seed < 0) { // randomize based on time for negative FRC position numbers
4351                 for(i=0; i<50; i++) seed += random();
4352                 seed = random() ^ random() >> 8 ^ random() << 8;
4353                 if(seed<0) seed = -seed;
4354         }
4355 }
4356
4357 int put(Board board, int pieceType, int rank, int n, int shade)
4358 // put the piece on the (n-1)-th empty squares of the given shade
4359 {
4360         int i;
4361
4362         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4363                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4364                         board[rank][i] = (ChessSquare) pieceType;
4365                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4366                         squaresLeft[ANY]--;
4367                         piecesLeft[pieceType]--; 
4368                         return i;
4369                 }
4370         }
4371         return -1;
4372 }
4373
4374
4375 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4376 // calculate where the next piece goes, (any empty square), and put it there
4377 {
4378         int i;
4379
4380         i = seed % squaresLeft[shade];
4381         nrOfShuffles *= squaresLeft[shade];
4382         seed /= squaresLeft[shade];
4383         put(board, pieceType, rank, i, shade);
4384 }
4385
4386 void AddTwoPieces(Board board, int pieceType, int rank)
4387 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4388 {
4389         int i, n=squaresLeft[ANY], j=n-1, k;
4390
4391         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4392         i = seed % k;  // pick one
4393         nrOfShuffles *= k;
4394         seed /= k;
4395         while(i >= j) i -= j--;
4396         j = n - 1 - j; i += j;
4397         put(board, pieceType, rank, j, ANY);
4398         put(board, pieceType, rank, i, ANY);
4399 }
4400
4401 void SetUpShuffle(Board board, int number)
4402 {
4403         int i, p, first=1;
4404
4405         GetPositionNumber(); nrOfShuffles = 1;
4406
4407         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4408         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4409         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4410
4411         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4412
4413         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4414             p = (int) board[0][i];
4415             if(p < (int) BlackPawn) piecesLeft[p] ++;
4416             board[0][i] = EmptySquare;
4417         }
4418
4419         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4420             // shuffles restricted to allow normal castling put KRR first
4421             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4422                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4423             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4424                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4425             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4426                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4427             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4428                 put(board, WhiteRook, 0, 0, ANY);
4429             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4430         }
4431
4432         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4433             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4434             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4435                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4436                 while(piecesLeft[p] >= 2) {
4437                     AddOnePiece(board, p, 0, LITE);
4438                     AddOnePiece(board, p, 0, DARK);
4439                 }
4440                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4441             }
4442
4443         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4444             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4445             // but we leave King and Rooks for last, to possibly obey FRC restriction
4446             if(p == (int)WhiteRook) continue;
4447             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4448             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4449         }
4450
4451         // now everything is placed, except perhaps King (Unicorn) and Rooks
4452
4453         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4454             // Last King gets castling rights
4455             while(piecesLeft[(int)WhiteUnicorn]) {
4456                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4457                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4458             }
4459
4460             while(piecesLeft[(int)WhiteKing]) {
4461                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4462                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4463             }
4464
4465
4466         } else {
4467             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4468             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4469         }
4470
4471         // Only Rooks can be left; simply place them all
4472         while(piecesLeft[(int)WhiteRook]) {
4473                 i = put(board, WhiteRook, 0, 0, ANY);
4474                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4475                         if(first) {
4476                                 first=0;
4477                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4478                         }
4479                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4480                 }
4481         }
4482         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4483             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4484         }
4485
4486         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4487 }
4488
4489 int SetCharTable( char *table, const char * map )
4490 /* [HGM] moved here from winboard.c because of its general usefulness */
4491 /*       Basically a safe strcpy that uses the last character as King */
4492 {
4493     int result = FALSE; int NrPieces;
4494
4495     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4496                     && NrPieces >= 12 && !(NrPieces&1)) {
4497         int i; /* [HGM] Accept even length from 12 to 34 */
4498
4499         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4500         for( i=0; i<NrPieces/2-1; i++ ) {
4501             table[i] = map[i];
4502             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4503         }
4504         table[(int) WhiteKing]  = map[NrPieces/2-1];
4505         table[(int) BlackKing]  = map[NrPieces-1];
4506
4507         result = TRUE;
4508     }
4509
4510     return result;
4511 }
4512
4513 void Prelude(Board board)
4514 {       // [HGM] superchess: random selection of exo-pieces
4515         int i, j, k; ChessSquare p; 
4516         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4517
4518         GetPositionNumber(); // use FRC position number
4519
4520         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4521             SetCharTable(pieceToChar, appData.pieceToCharTable);
4522             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4523                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4524         }
4525
4526         j = seed%4;                 seed /= 4; 
4527         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4528         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4529         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4530         j = seed%3 + (seed%3 >= j); seed /= 3; 
4531         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4532         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4533         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4534         j = seed%3;                 seed /= 3; 
4535         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4536         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4537         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4538         j = seed%2 + (seed%2 >= j); seed /= 2; 
4539         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4540         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4541         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4542         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4543         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4544         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4545         put(board, exoPieces[0],    0, 0, ANY);
4546         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4547 }
4548
4549 void
4550 InitPosition(redraw)
4551      int redraw;
4552 {
4553     ChessSquare (* pieces)[BOARD_SIZE];
4554     int i, j, pawnRow, overrule,
4555     oldx = gameInfo.boardWidth,
4556     oldy = gameInfo.boardHeight,
4557     oldh = gameInfo.holdingsWidth,
4558     oldv = gameInfo.variant;
4559
4560     currentMove = forwardMostMove = backwardMostMove = 0;
4561     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4562
4563     /* [AS] Initialize pv info list [HGM] and game status */
4564     {
4565         for( i=0; i<MAX_MOVES; i++ ) {
4566             pvInfoList[i].depth = 0;
4567             epStatus[i]=EP_NONE;
4568             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4569         }
4570
4571         initialRulePlies = 0; /* 50-move counter start */
4572
4573         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4574         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4575     }
4576
4577     
4578     /* [HGM] logic here is completely changed. In stead of full positions */
4579     /* the initialized data only consist of the two backranks. The switch */
4580     /* selects which one we will use, which is than copied to the Board   */
4581     /* initialPosition, which for the rest is initialized by Pawns and    */
4582     /* empty squares. This initial position is then copied to boards[0],  */
4583     /* possibly after shuffling, so that it remains available.            */
4584
4585     gameInfo.holdingsWidth = 0; /* default board sizes */
4586     gameInfo.boardWidth    = 8;
4587     gameInfo.boardHeight   = 8;
4588     gameInfo.holdingsSize  = 0;
4589     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4590     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4591     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4592
4593     switch (gameInfo.variant) {
4594     case VariantFischeRandom:
4595       shuffleOpenings = TRUE;
4596     default:
4597       pieces = FIDEArray;
4598       break;
4599     case VariantShatranj:
4600       pieces = ShatranjArray;
4601       nrCastlingRights = 0;
4602       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4603       break;
4604     case VariantTwoKings:
4605       pieces = twoKingsArray;
4606       break;
4607     case VariantCapaRandom:
4608       shuffleOpenings = TRUE;
4609     case VariantCapablanca:
4610       pieces = CapablancaArray;
4611       gameInfo.boardWidth = 10;
4612       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4613       break;
4614     case VariantGothic:
4615       pieces = GothicArray;
4616       gameInfo.boardWidth = 10;
4617       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4618       break;
4619     case VariantJanus:
4620       pieces = JanusArray;
4621       gameInfo.boardWidth = 10;
4622       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4623       nrCastlingRights = 6;
4624         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4625         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4626         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4627         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4628         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4629         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4630       break;
4631     case VariantFalcon:
4632       pieces = FalconArray;
4633       gameInfo.boardWidth = 10;
4634       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4635       break;
4636     case VariantXiangqi:
4637       pieces = XiangqiArray;
4638       gameInfo.boardWidth  = 9;
4639       gameInfo.boardHeight = 10;
4640       nrCastlingRights = 0;
4641       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4642       break;
4643     case VariantShogi:
4644       pieces = ShogiArray;
4645       gameInfo.boardWidth  = 9;
4646       gameInfo.boardHeight = 9;
4647       gameInfo.holdingsSize = 7;
4648       nrCastlingRights = 0;
4649       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4650       break;
4651     case VariantCourier:
4652       pieces = CourierArray;
4653       gameInfo.boardWidth  = 12;
4654       nrCastlingRights = 0;
4655       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4656       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4657       break;
4658     case VariantKnightmate:
4659       pieces = KnightmateArray;
4660       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4661       break;
4662     case VariantFairy:
4663       pieces = fairyArray;
4664       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4665       break;
4666     case VariantGreat:
4667       pieces = GreatArray;
4668       gameInfo.boardWidth = 10;
4669       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4670       gameInfo.holdingsSize = 8;
4671       break;
4672     case VariantSuper:
4673       pieces = FIDEArray;
4674       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4675       gameInfo.holdingsSize = 8;
4676       startedFromSetupPosition = TRUE;
4677       break;
4678     case VariantCrazyhouse:
4679     case VariantBughouse:
4680       pieces = FIDEArray;
4681       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4682       gameInfo.holdingsSize = 5;
4683       break;
4684     case VariantWildCastle:
4685       pieces = FIDEArray;
4686       /* !!?shuffle with kings guaranteed to be on d or e file */
4687       shuffleOpenings = 1;
4688       break;
4689     case VariantNoCastle:
4690       pieces = FIDEArray;
4691       nrCastlingRights = 0;
4692       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4693       /* !!?unconstrained back-rank shuffle */
4694       shuffleOpenings = 1;
4695       break;
4696     }
4697
4698     overrule = 0;
4699     if(appData.NrFiles >= 0) {
4700         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4701         gameInfo.boardWidth = appData.NrFiles;
4702     }
4703     if(appData.NrRanks >= 0) {
4704         gameInfo.boardHeight = appData.NrRanks;
4705     }
4706     if(appData.holdingsSize >= 0) {
4707         i = appData.holdingsSize;
4708         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4709         gameInfo.holdingsSize = i;
4710     }
4711     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4712     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4713         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4714
4715     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4716     if(pawnRow < 1) pawnRow = 1;
4717
4718     /* User pieceToChar list overrules defaults */
4719     if(appData.pieceToCharTable != NULL)
4720         SetCharTable(pieceToChar, appData.pieceToCharTable);
4721
4722     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4723
4724         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4725             s = (ChessSquare) 0; /* account holding counts in guard band */
4726         for( i=0; i<BOARD_HEIGHT; i++ )
4727             initialPosition[i][j] = s;
4728
4729         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4730         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4731         initialPosition[pawnRow][j] = WhitePawn;
4732         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4733         if(gameInfo.variant == VariantXiangqi) {
4734             if(j&1) {
4735                 initialPosition[pawnRow][j] = 
4736                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4737                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4738                    initialPosition[2][j] = WhiteCannon;
4739                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4740                 }
4741             }
4742         }
4743         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4744     }
4745     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4746
4747             j=BOARD_LEFT+1;
4748             initialPosition[1][j] = WhiteBishop;
4749             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4750             j=BOARD_RGHT-2;
4751             initialPosition[1][j] = WhiteRook;
4752             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4753     }
4754
4755     if( nrCastlingRights == -1) {
4756         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4757         /*       This sets default castling rights from none to normal corners   */
4758         /* Variants with other castling rights must set them themselves above    */
4759         nrCastlingRights = 6;
4760        
4761         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4762         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4763         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4764         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4765         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4766         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4767      }
4768
4769      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4770      if(gameInfo.variant == VariantGreat) { // promotion commoners
4771         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4772         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4773         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4774         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4775      }
4776 #if 0
4777     if(gameInfo.variant == VariantFischeRandom) {
4778       if( appData.defaultFrcPosition < 0 ) {
4779         ShuffleFRC( initialPosition );
4780       }
4781       else {
4782         SetupFRC( initialPosition, appData.defaultFrcPosition );
4783       }
4784       startedFromSetupPosition = TRUE;
4785     } else 
4786 #else
4787   if (appData.debugMode) {
4788     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4789   }
4790     if(shuffleOpenings) {
4791         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4792         startedFromSetupPosition = TRUE;
4793     }
4794 #endif
4795     if(startedFromPositionFile) {
4796       /* [HGM] loadPos: use PositionFile for every new game */
4797       CopyBoard(initialPosition, filePosition);
4798       for(i=0; i<nrCastlingRights; i++)
4799           castlingRights[0][i] = initialRights[i] = fileRights[i];
4800       startedFromSetupPosition = TRUE;
4801     }
4802
4803     CopyBoard(boards[0], initialPosition);
4804
4805     if(oldx != gameInfo.boardWidth ||
4806        oldy != gameInfo.boardHeight ||
4807        oldh != gameInfo.holdingsWidth
4808 #ifdef GOTHIC
4809        || oldv == VariantGothic ||        // For licensing popups
4810        gameInfo.variant == VariantGothic
4811 #endif
4812 #ifdef FALCON
4813        || oldv == VariantFalcon ||
4814        gameInfo.variant == VariantFalcon
4815 #endif
4816                                          )
4817             InitDrawingSizes(-2 ,0);
4818
4819     if (redraw)
4820       DrawPosition(TRUE, boards[currentMove]);
4821 }
4822
4823 void
4824 SendBoard(cps, moveNum)
4825      ChessProgramState *cps;
4826      int moveNum;
4827 {
4828     char message[MSG_SIZ];
4829     
4830     if (cps->useSetboard) {
4831       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4832       sprintf(message, "setboard %s\n", fen);
4833       SendToProgram(message, cps);
4834       free(fen);
4835
4836     } else {
4837       ChessSquare *bp;
4838       int i, j;
4839       /* Kludge to set black to move, avoiding the troublesome and now
4840        * deprecated "black" command.
4841        */
4842       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4843
4844       SendToProgram("edit\n", cps);
4845       SendToProgram("#\n", cps);
4846       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4847         bp = &boards[moveNum][i][BOARD_LEFT];
4848         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4849           if ((int) *bp < (int) BlackPawn) {
4850             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4851                     AAA + j, ONE + i);
4852             if(message[0] == '+' || message[0] == '~') {
4853                 sprintf(message, "%c%c%c+\n",
4854                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4855                         AAA + j, ONE + i);
4856             }
4857             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4858                 message[1] = BOARD_RGHT   - 1 - j + '1';
4859                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4860             }
4861             SendToProgram(message, cps);
4862           }
4863         }
4864       }
4865     
4866       SendToProgram("c\n", cps);
4867       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4868         bp = &boards[moveNum][i][BOARD_LEFT];
4869         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4870           if (((int) *bp != (int) EmptySquare)
4871               && ((int) *bp >= (int) BlackPawn)) {
4872             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4873                     AAA + j, ONE + i);
4874             if(message[0] == '+' || message[0] == '~') {
4875                 sprintf(message, "%c%c%c+\n",
4876                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4877                         AAA + j, ONE + i);
4878             }
4879             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4880                 message[1] = BOARD_RGHT   - 1 - j + '1';
4881                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4882             }
4883             SendToProgram(message, cps);
4884           }
4885         }
4886       }
4887     
4888       SendToProgram(".\n", cps);
4889     }
4890     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4891 }
4892
4893 int
4894 IsPromotion(fromX, fromY, toX, toY)
4895      int fromX, fromY, toX, toY;
4896 {
4897     /* [HGM] add Shogi promotions */
4898     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4899     ChessSquare piece;
4900
4901     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4902       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4903    /* [HGM] Note to self: line above also weeds out drops */
4904     piece = boards[currentMove][fromY][fromX];
4905     if(gameInfo.variant == VariantShogi) {
4906         promotionZoneSize = 3;
4907         highestPromotingPiece = (int)WhiteKing;
4908         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4909            and if in normal chess we then allow promotion to King, why not
4910            allow promotion of other piece in Shogi?                         */
4911     }
4912     if((int)piece >= BlackPawn) {
4913         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4914              return FALSE;
4915         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4916     } else {
4917         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4918            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4919     }
4920     return ( (int)piece <= highestPromotingPiece );
4921 }
4922
4923 int
4924 InPalace(row, column)
4925      int row, column;
4926 {   /* [HGM] for Xiangqi */
4927     if( (row < 3 || row > BOARD_HEIGHT-4) &&
4928          column < (BOARD_WIDTH + 4)/2 &&
4929          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4930     return FALSE;
4931 }
4932
4933 int
4934 PieceForSquare (x, y)
4935      int x;
4936      int y;
4937 {
4938   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4939      return -1;
4940   else
4941      return boards[currentMove][y][x];
4942 }
4943
4944 int
4945 OKToStartUserMove(x, y)
4946      int x, y;
4947 {
4948     ChessSquare from_piece;
4949     int white_piece;
4950
4951     if (matchMode) return FALSE;
4952     if (gameMode == EditPosition) return TRUE;
4953
4954     if (x >= 0 && y >= 0)
4955       from_piece = boards[currentMove][y][x];
4956     else
4957       from_piece = EmptySquare;
4958
4959     if (from_piece == EmptySquare) return FALSE;
4960
4961     white_piece = (int)from_piece >= (int)WhitePawn &&
4962       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4963
4964     switch (gameMode) {
4965       case PlayFromGameFile:
4966       case AnalyzeFile:
4967       case TwoMachinesPlay:
4968       case EndOfGame:
4969         return FALSE;
4970
4971       case IcsObserving:
4972       case IcsIdle:
4973         return FALSE;
4974
4975       case MachinePlaysWhite:
4976       case IcsPlayingBlack:
4977         if (appData.zippyPlay) return FALSE;
4978         if (white_piece) {
4979             DisplayMoveError(_("You are playing Black"));
4980             return FALSE;
4981         }
4982         break;
4983
4984       case MachinePlaysBlack:
4985       case IcsPlayingWhite:
4986         if (appData.zippyPlay) return FALSE;
4987         if (!white_piece) {
4988             DisplayMoveError(_("You are playing White"));
4989             return FALSE;
4990         }
4991         break;
4992
4993       case EditGame:
4994         if (!white_piece && WhiteOnMove(currentMove)) {
4995             DisplayMoveError(_("It is White's turn"));
4996             return FALSE;
4997         }           
4998         if (white_piece && !WhiteOnMove(currentMove)) {
4999             DisplayMoveError(_("It is Black's turn"));
5000             return FALSE;
5001         }           
5002         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5003             /* Editing correspondence game history */
5004             /* Could disallow this or prompt for confirmation */
5005             cmailOldMove = -1;
5006         }
5007         if (currentMove < forwardMostMove) {
5008             /* Discarding moves */
5009             /* Could prompt for confirmation here,
5010                but I don't think that's such a good idea */
5011             forwardMostMove = currentMove;
5012         }
5013         break;
5014
5015       case BeginningOfGame:
5016         if (appData.icsActive) return FALSE;
5017         if (!appData.noChessProgram) {
5018             if (!white_piece) {
5019                 DisplayMoveError(_("You are playing White"));
5020                 return FALSE;
5021             }
5022         }
5023         break;
5024         
5025       case Training:
5026         if (!white_piece && WhiteOnMove(currentMove)) {
5027             DisplayMoveError(_("It is White's turn"));
5028             return FALSE;
5029         }           
5030         if (white_piece && !WhiteOnMove(currentMove)) {
5031             DisplayMoveError(_("It is Black's turn"));
5032             return FALSE;
5033         }           
5034         break;
5035
5036       default:
5037       case IcsExamining:
5038         break;
5039     }
5040     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5041         && gameMode != AnalyzeFile && gameMode != Training) {
5042         DisplayMoveError(_("Displayed position is not current"));
5043         return FALSE;
5044     }
5045     return TRUE;
5046 }
5047
5048 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5049 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5050 int lastLoadGameUseList = FALSE;
5051 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5052 ChessMove lastLoadGameStart = (ChessMove) 0;
5053
5054
5055 ChessMove
5056 UserMoveTest(fromX, fromY, toX, toY, promoChar)
5057      int fromX, fromY, toX, toY;
5058      int promoChar;
5059 {
5060     ChessMove moveType;
5061     ChessSquare pdown, pup;
5062
5063     if (fromX < 0 || fromY < 0) return ImpossibleMove;
5064     if ((fromX == toX) && (fromY == toY)) {
5065         return ImpossibleMove;
5066     }
5067
5068     /* [HGM] suppress all moves into holdings area and guard band */
5069     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5070             return ImpossibleMove;
5071
5072     /* [HGM] <sameColor> moved to here from winboard.c */
5073     /* note: this code seems to exist for filtering out some obviously illegal premoves */
5074     pdown = boards[currentMove][fromY][fromX];
5075     pup = boards[currentMove][toY][toX];
5076     if (    gameMode != EditPosition &&
5077             (WhitePawn <= pdown && pdown < BlackPawn &&
5078              WhitePawn <= pup && pup < BlackPawn  ||
5079              BlackPawn <= pdown && pdown < EmptySquare &&
5080              BlackPawn <= pup && pup < EmptySquare 
5081             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5082                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5083                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) 
5084         )           )
5085          return ImpossibleMove;
5086
5087     /* Check if the user is playing in turn.  This is complicated because we
5088        let the user "pick up" a piece before it is his turn.  So the piece he
5089        tried to pick up may have been captured by the time he puts it down!
5090        Therefore we use the color the user is supposed to be playing in this
5091        test, not the color of the piece that is currently on the starting
5092        square---except in EditGame mode, where the user is playing both
5093        sides; fortunately there the capture race can't happen.  (It can
5094        now happen in IcsExamining mode, but that's just too bad.  The user
5095        will get a somewhat confusing message in that case.)
5096        */
5097
5098     switch (gameMode) {
5099       case PlayFromGameFile:
5100       case AnalyzeFile:
5101       case TwoMachinesPlay:
5102       case EndOfGame:
5103       case IcsObserving:
5104       case IcsIdle:
5105         /* We switched into a game mode where moves are not accepted,
5106            perhaps while the mouse button was down. */
5107         return ImpossibleMove;
5108
5109       case MachinePlaysWhite:
5110         /* User is moving for Black */
5111         if (WhiteOnMove(currentMove)) {
5112             DisplayMoveError(_("It is White's turn"));
5113             return ImpossibleMove;
5114         }
5115         break;
5116
5117       case MachinePlaysBlack:
5118         /* User is moving for White */
5119         if (!WhiteOnMove(currentMove)) {
5120             DisplayMoveError(_("It is Black's turn"));
5121             return ImpossibleMove;
5122         }
5123         break;
5124
5125       case EditGame:
5126       case IcsExamining:
5127       case BeginningOfGame:
5128       case AnalyzeMode:
5129       case Training:
5130         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5131             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5132             /* User is moving for Black */
5133             if (WhiteOnMove(currentMove)) {
5134                 DisplayMoveError(_("It is White's turn"));
5135                 return ImpossibleMove;
5136             }
5137         } else {
5138             /* User is moving for White */
5139             if (!WhiteOnMove(currentMove)) {
5140                 DisplayMoveError(_("It is Black's turn"));
5141                 return ImpossibleMove;
5142             }
5143         }
5144         break;
5145
5146       case IcsPlayingBlack:
5147         /* User is moving for Black */
5148         if (WhiteOnMove(currentMove)) {
5149             if (!appData.premove) {
5150                 DisplayMoveError(_("It is White's turn"));
5151             } else if (toX >= 0 && toY >= 0) {
5152                 premoveToX = toX;
5153                 premoveToY = toY;
5154                 premoveFromX = fromX;
5155                 premoveFromY = fromY;
5156                 premovePromoChar = promoChar;
5157                 gotPremove = 1;
5158                 if (appData.debugMode) 
5159                     fprintf(debugFP, "Got premove: fromX %d,"
5160                             "fromY %d, toX %d, toY %d\n",
5161                             fromX, fromY, toX, toY);
5162             }
5163             return ImpossibleMove;
5164         }
5165         break;
5166
5167       case IcsPlayingWhite:
5168         /* User is moving for White */
5169         if (!WhiteOnMove(currentMove)) {
5170             if (!appData.premove) {
5171                 DisplayMoveError(_("It is Black's turn"));
5172             } else if (toX >= 0 && toY >= 0) {
5173                 premoveToX = toX;
5174                 premoveToY = toY;
5175                 premoveFromX = fromX;
5176                 premoveFromY = fromY;
5177                 premovePromoChar = promoChar;
5178                 gotPremove = 1;
5179                 if (appData.debugMode) 
5180                     fprintf(debugFP, "Got premove: fromX %d,"
5181                             "fromY %d, toX %d, toY %d\n",
5182                             fromX, fromY, toX, toY);
5183             }
5184             return ImpossibleMove;
5185         }
5186         break;
5187
5188       default:
5189         break;
5190
5191       case EditPosition:
5192         /* EditPosition, empty square, or different color piece;
5193            click-click move is possible */
5194         if (toX == -2 || toY == -2) {
5195             boards[0][fromY][fromX] = EmptySquare;
5196             return AmbiguousMove;
5197         } else if (toX >= 0 && toY >= 0) {
5198             boards[0][toY][toX] = boards[0][fromY][fromX];
5199             boards[0][fromY][fromX] = EmptySquare;
5200             return AmbiguousMove;
5201         }
5202         return ImpossibleMove;
5203     }
5204
5205     /* [HGM] If move started in holdings, it means a drop */
5206     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5207          if( pup != EmptySquare ) return ImpossibleMove;
5208          if(appData.testLegality) {
5209              /* it would be more logical if LegalityTest() also figured out
5210               * which drops are legal. For now we forbid pawns on back rank.
5211               * Shogi is on its own here...
5212               */
5213              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5214                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5215                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5216          }
5217          return WhiteDrop; /* Not needed to specify white or black yet */
5218     }
5219
5220     userOfferedDraw = FALSE;
5221         
5222     /* [HGM] always test for legality, to get promotion info */
5223     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5224                           epStatus[currentMove], castlingRights[currentMove],
5225                                          fromY, fromX, toY, toX, promoChar);
5226
5227     /* [HGM] but possibly ignore an IllegalMove result */
5228     if (appData.testLegality) {
5229         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5230             DisplayMoveError(_("Illegal move"));
5231             return ImpossibleMove;
5232         }
5233     }
5234 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5235     return moveType;
5236     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5237        function is made into one that returns an OK move type if FinishMove
5238        should be called. This to give the calling driver routine the
5239        opportunity to finish the userMove input with a promotion popup,
5240        without bothering the user with this for invalid or illegal moves */
5241
5242 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5243 }
5244
5245 /* Common tail of UserMoveEvent and DropMenuEvent */
5246 int
5247 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5248      ChessMove moveType;
5249      int fromX, fromY, toX, toY;
5250      /*char*/int promoChar;
5251 {
5252     char *bookHit = 0;
5253 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5254     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5255         // [HGM] superchess: suppress promotions to non-available piece
5256         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5257         if(WhiteOnMove(currentMove)) {
5258             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5259         } else {
5260             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5261         }
5262     }
5263
5264     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5265        move type in caller when we know the move is a legal promotion */
5266     if(moveType == NormalMove && promoChar)
5267         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5268 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5269     /* [HGM] convert drag-and-drop piece drops to standard form */
5270     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5271          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5272            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5273                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5274 //         fromX = boards[currentMove][fromY][fromX];
5275            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5276            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5277            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5278            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5279          fromY = DROP_RANK;
5280     }
5281
5282     /* [HGM] <popupFix> The following if has been moved here from
5283        UserMoveEvent(). Because it seemed to belon here (why not allow
5284        piece drops in training games?), and because it can only be
5285        performed after it is known to what we promote. */
5286     if (gameMode == Training) {
5287       /* compare the move played on the board to the next move in the
5288        * game. If they match, display the move and the opponent's response. 
5289        * If they don't match, display an error message.
5290        */
5291       int saveAnimate;
5292       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5293       CopyBoard(testBoard, boards[currentMove]);
5294       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5295
5296       if (CompareBoards(testBoard, boards[currentMove+1])) {
5297         ForwardInner(currentMove+1);
5298
5299         /* Autoplay the opponent's response.
5300          * if appData.animate was TRUE when Training mode was entered,
5301          * the response will be animated.
5302          */
5303         saveAnimate = appData.animate;
5304         appData.animate = animateTraining;
5305         ForwardInner(currentMove+1);
5306         appData.animate = saveAnimate;
5307
5308         /* check for the end of the game */
5309         if (currentMove >= forwardMostMove) {
5310           gameMode = PlayFromGameFile;
5311           ModeHighlight();
5312           SetTrainingModeOff();
5313           DisplayInformation(_("End of game"));
5314         }
5315       } else {
5316         DisplayError(_("Incorrect move"), 0);
5317       }
5318       return 1;
5319     }
5320
5321   /* Ok, now we know that the move is good, so we can kill
5322      the previous line in Analysis Mode */
5323   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5324     forwardMostMove = currentMove;
5325   }
5326
5327   /* If we need the chess program but it's dead, restart it */
5328   ResurrectChessProgram();
5329
5330   /* A user move restarts a paused game*/
5331   if (pausing)
5332     PauseEvent();
5333
5334   thinkOutput[0] = NULLCHAR;
5335
5336   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5337
5338   if (gameMode == BeginningOfGame) {
5339     if (appData.noChessProgram) {
5340       gameMode = EditGame;
5341       SetGameInfo();
5342     } else {
5343       char buf[MSG_SIZ];
5344       gameMode = MachinePlaysBlack;
5345       StartClocks();
5346       SetGameInfo();
5347       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5348       DisplayTitle(buf);
5349       if (first.sendName) {
5350         sprintf(buf, "name %s\n", gameInfo.white);
5351         SendToProgram(buf, &first);
5352       }
5353       StartClocks();
5354     }
5355     ModeHighlight();
5356   }
5357 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5358   /* Relay move to ICS or chess engine */
5359   if (appData.icsActive) {
5360     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5361         gameMode == IcsExamining) {
5362       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5363       ics_user_moved = 1;
5364     }
5365   } else {
5366     if (first.sendTime && (gameMode == BeginningOfGame ||
5367                            gameMode == MachinePlaysWhite ||
5368                            gameMode == MachinePlaysBlack)) {
5369       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5370     }
5371     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5372          // [HGM] book: if program might be playing, let it use book
5373         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5374         first.maybeThinking = TRUE;
5375     } else SendMoveToProgram(forwardMostMove-1, &first);
5376     if (currentMove == cmailOldMove + 1) {
5377       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5378     }
5379   }
5380
5381   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5382
5383   switch (gameMode) {
5384   case EditGame:
5385     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5386                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5387     case MT_NONE:
5388     case MT_CHECK:
5389       break;
5390     case MT_CHECKMATE:
5391     case MT_STAINMATE:
5392       if (WhiteOnMove(currentMove)) {
5393         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5394       } else {
5395         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5396       }
5397       break;
5398     case MT_STALEMATE:
5399       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5400       break;
5401     }
5402     break;
5403     
5404   case MachinePlaysBlack:
5405   case MachinePlaysWhite:
5406     /* disable certain menu options while machine is thinking */
5407     SetMachineThinkingEnables();
5408     break;
5409
5410   default:
5411     break;
5412   }
5413
5414   if(bookHit) { // [HGM] book: simulate book reply
5415         static char bookMove[MSG_SIZ]; // a bit generous?
5416
5417         programStats.nodes = programStats.depth = programStats.time = 
5418         programStats.score = programStats.got_only_move = 0;
5419         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5420
5421         strcpy(bookMove, "move ");
5422         strcat(bookMove, bookHit);
5423         HandleMachineMove(bookMove, &first);
5424   }
5425   return 1;
5426 }
5427
5428 void
5429 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5430      int fromX, fromY, toX, toY;
5431      int promoChar;
5432 {
5433     /* [HGM] This routine was added to allow calling of its two logical
5434        parts from other modules in the old way. Before, UserMoveEvent()
5435        automatically called FinishMove() if the move was OK, and returned
5436        otherwise. I separated the two, in order to make it possible to
5437        slip a promotion popup in between. But that it always needs two
5438        calls, to the first part, (now called UserMoveTest() ), and to
5439        FinishMove if the first part succeeded. Calls that do not need
5440        to do anything in between, can call this routine the old way. 
5441     */
5442     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
5443 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5444     if(moveType != ImpossibleMove)
5445         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5446 }
5447
5448 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5449 {
5450 //    char * hint = lastHint;
5451     FrontEndProgramStats stats;
5452
5453     stats.which = cps == &first ? 0 : 1;
5454     stats.depth = cpstats->depth;
5455     stats.nodes = cpstats->nodes;
5456     stats.score = cpstats->score;
5457     stats.time = cpstats->time;
5458     stats.pv = cpstats->movelist;
5459     stats.hint = lastHint;
5460     stats.an_move_index = 0;
5461     stats.an_move_count = 0;
5462
5463     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5464         stats.hint = cpstats->move_name;
5465         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5466         stats.an_move_count = cpstats->nr_moves;
5467     }
5468
5469     SetProgramStats( &stats );
5470 }
5471
5472 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5473 {   // [HGM] book: this routine intercepts moves to simulate book replies
5474     char *bookHit = NULL;
5475
5476     //first determine if the incoming move brings opponent into his book
5477     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5478         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5479     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5480     if(bookHit != NULL && !cps->bookSuspend) {
5481         // make sure opponent is not going to reply after receiving move to book position
5482         SendToProgram("force\n", cps);
5483         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5484     }
5485     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5486     // now arrange restart after book miss
5487     if(bookHit) {
5488         // after a book hit we never send 'go', and the code after the call to this routine
5489         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5490         char buf[MSG_SIZ];
5491         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5492         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5493         SendToProgram(buf, cps);
5494         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5495     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5496         SendToProgram("go\n", cps);
5497         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5498     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5499         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5500             SendToProgram("go\n", cps); 
5501         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5502     }
5503     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5504 }
5505
5506 char *savedMessage;
5507 ChessProgramState *savedState;
5508 void DeferredBookMove(void)
5509 {
5510         if(savedState->lastPing != savedState->lastPong)
5511                     ScheduleDelayedEvent(DeferredBookMove, 10);
5512         else
5513         HandleMachineMove(savedMessage, savedState);
5514 }
5515
5516 void
5517 HandleMachineMove(message, cps)
5518      char *message;
5519      ChessProgramState *cps;
5520 {
5521     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5522     char realname[MSG_SIZ];
5523     int fromX, fromY, toX, toY;
5524     ChessMove moveType;
5525     char promoChar;
5526     char *p;
5527     int machineWhite;
5528     char *bookHit;
5529
5530 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5531     /*
5532      * Kludge to ignore BEL characters
5533      */
5534     while (*message == '\007') message++;
5535
5536     /*
5537      * [HGM] engine debug message: ignore lines starting with '#' character
5538      */
5539     if(cps->debug && *message == '#') return;
5540
5541     /*
5542      * Look for book output
5543      */
5544     if (cps == &first && bookRequested) {
5545         if (message[0] == '\t' || message[0] == ' ') {
5546             /* Part of the book output is here; append it */
5547             strcat(bookOutput, message);
5548             strcat(bookOutput, "  \n");
5549             return;
5550         } else if (bookOutput[0] != NULLCHAR) {
5551             /* All of book output has arrived; display it */
5552             char *p = bookOutput;
5553             while (*p != NULLCHAR) {
5554                 if (*p == '\t') *p = ' ';
5555                 p++;
5556             }
5557             DisplayInformation(bookOutput);
5558             bookRequested = FALSE;
5559             /* Fall through to parse the current output */
5560         }
5561     }
5562
5563     /*
5564      * Look for machine move.
5565      */
5566     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5567         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5568     {
5569         /* This method is only useful on engines that support ping */
5570         if (cps->lastPing != cps->lastPong) {
5571           if (gameMode == BeginningOfGame) {
5572             /* Extra move from before last new; ignore */
5573             if (appData.debugMode) {
5574                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5575             }
5576           } else {
5577             if (appData.debugMode) {
5578                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5579                         cps->which, gameMode);
5580             }
5581
5582             SendToProgram("undo\n", cps);
5583           }
5584           return;
5585         }
5586
5587         switch (gameMode) {
5588           case BeginningOfGame:
5589             /* Extra move from before last reset; ignore */
5590             if (appData.debugMode) {
5591                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5592             }
5593             return;
5594
5595           case EndOfGame:
5596           case IcsIdle:
5597           default:
5598             /* Extra move after we tried to stop.  The mode test is
5599                not a reliable way of detecting this problem, but it's
5600                the best we can do on engines that don't support ping.
5601             */
5602             if (appData.debugMode) {
5603                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5604                         cps->which, gameMode);
5605             }
5606             SendToProgram("undo\n", cps);
5607             return;
5608
5609           case MachinePlaysWhite:
5610           case IcsPlayingWhite:
5611             machineWhite = TRUE;
5612             break;
5613
5614           case MachinePlaysBlack:
5615           case IcsPlayingBlack:
5616             machineWhite = FALSE;
5617             break;
5618
5619           case TwoMachinesPlay:
5620             machineWhite = (cps->twoMachinesColor[0] == 'w');
5621             break;
5622         }
5623         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5624             if (appData.debugMode) {
5625                 fprintf(debugFP,
5626                         "Ignoring move out of turn by %s, gameMode %d"
5627                         ", forwardMost %d\n",
5628                         cps->which, gameMode, forwardMostMove);
5629             }
5630             return;
5631         }
5632
5633     if (appData.debugMode) { int f = forwardMostMove;
5634         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5635                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5636     }
5637         if(cps->alphaRank) AlphaRank(machineMove, 4);
5638         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5639                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5640             /* Machine move could not be parsed; ignore it. */
5641             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5642                     machineMove, cps->which);
5643             DisplayError(buf1, 0);
5644             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5645                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5646             if (gameMode == TwoMachinesPlay) {
5647               GameEnds(machineWhite ? BlackWins : WhiteWins,
5648                        buf1, GE_XBOARD);
5649             }
5650             return;
5651         }
5652
5653         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5654         /* So we have to redo legality test with true e.p. status here,  */
5655         /* to make sure an illegal e.p. capture does not slip through,   */
5656         /* to cause a forfeit on a justified illegal-move complaint      */
5657         /* of the opponent.                                              */
5658         if( gameMode==TwoMachinesPlay && appData.testLegality
5659             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5660                                                               ) {
5661            ChessMove moveType;
5662            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5663                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5664                              fromY, fromX, toY, toX, promoChar);
5665             if (appData.debugMode) {
5666                 int i;
5667                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5668                     castlingRights[forwardMostMove][i], castlingRank[i]);
5669                 fprintf(debugFP, "castling rights\n");
5670             }
5671             if(moveType == IllegalMove) {
5672                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5673                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5674                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5675                            buf1, GE_XBOARD);
5676                 return;
5677            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5678            /* [HGM] Kludge to handle engines that send FRC-style castling
5679               when they shouldn't (like TSCP-Gothic) */
5680            switch(moveType) {
5681              case WhiteASideCastleFR:
5682              case BlackASideCastleFR:
5683                toX+=2;
5684                currentMoveString[2]++;
5685                break;
5686              case WhiteHSideCastleFR:
5687              case BlackHSideCastleFR:
5688                toX--;
5689                currentMoveString[2]--;
5690                break;
5691              default: ; // nothing to do, but suppresses warning of pedantic compilers
5692            }
5693         }
5694         hintRequested = FALSE;
5695         lastHint[0] = NULLCHAR;
5696         bookRequested = FALSE;
5697         /* Program may be pondering now */
5698         cps->maybeThinking = TRUE;
5699         if (cps->sendTime == 2) cps->sendTime = 1;
5700         if (cps->offeredDraw) cps->offeredDraw--;
5701
5702 #if ZIPPY
5703         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5704             first.initDone) {
5705           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5706           ics_user_moved = 1;
5707           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5708                 char buf[3*MSG_SIZ];
5709
5710                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5711                         programStats.score / 100.,
5712                         programStats.depth,
5713                         programStats.time / 100.,
5714                         (unsigned int)programStats.nodes,
5715                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5716                         programStats.movelist);
5717                 SendToICS(buf);
5718 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5719           }
5720         }
5721 #endif
5722         /* currentMoveString is set as a side-effect of ParseOneMove */
5723         strcpy(machineMove, currentMoveString);
5724         strcat(machineMove, "\n");
5725         strcpy(moveList[forwardMostMove], machineMove);
5726
5727         /* [AS] Save move info and clear stats for next move */
5728         pvInfoList[ forwardMostMove ].score = programStats.score;
5729         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5730         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5731         ClearProgramStats();
5732         thinkOutput[0] = NULLCHAR;
5733         hiddenThinkOutputState = 0;
5734
5735         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5736
5737         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5738         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5739             int count = 0;
5740
5741             while( count < adjudicateLossPlies ) {
5742                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5743
5744                 if( count & 1 ) {
5745                     score = -score; /* Flip score for winning side */
5746                 }
5747
5748                 if( score > adjudicateLossThreshold ) {
5749                     break;
5750                 }
5751
5752                 count++;
5753             }
5754
5755             if( count >= adjudicateLossPlies ) {
5756                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5757
5758                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5759                     "Xboard adjudication", 
5760                     GE_XBOARD );
5761
5762                 return;
5763             }
5764         }
5765
5766         if( gameMode == TwoMachinesPlay ) {
5767           // [HGM] some adjudications useful with buggy engines
5768             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5769           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5770
5771
5772             if( appData.testLegality )
5773             {   /* [HGM] Some more adjudications for obstinate engines */
5774                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5775                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5776                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5777                 static int moveCount = 6;
5778                 ChessMove result;
5779                 char *reason = NULL;
5780
5781                 /* Count what is on board. */
5782                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5783                 {   ChessSquare p = boards[forwardMostMove][i][j];
5784                     int m=i;
5785
5786                     switch((int) p)
5787                     {   /* count B,N,R and other of each side */
5788                         case WhiteKing:
5789                         case BlackKing:
5790                              NrK++; break; // [HGM] atomic: count Kings
5791                         case WhiteKnight:
5792                              NrWN++; break;
5793                         case WhiteBishop:
5794                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5795                              bishopsColor |= 1 << ((i^j)&1);
5796                              NrWB++; break;
5797                         case BlackKnight:
5798                              NrBN++; break;
5799                         case BlackBishop:
5800                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5801                              bishopsColor |= 1 << ((i^j)&1);
5802                              NrBB++; break;
5803                         case WhiteRook:
5804                              NrWR++; break;
5805                         case BlackRook:
5806                              NrBR++; break;
5807                         case WhiteQueen:
5808                              NrWQ++; break;
5809                         case BlackQueen:
5810                              NrBQ++; break;
5811                         case EmptySquare: 
5812                              break;
5813                         case BlackPawn:
5814                              m = 7-i;
5815                         case WhitePawn:
5816                              PawnAdvance += m; NrPawns++;
5817                     }
5818                     NrPieces += (p != EmptySquare);
5819                     NrW += ((int)p < (int)BlackPawn);
5820                     if(gameInfo.variant == VariantXiangqi && 
5821                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5822                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5823                         NrW -= ((int)p < (int)BlackPawn);
5824                     }
5825                 }
5826
5827                 /* Some material-based adjudications that have to be made before stalemate test */
5828                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5829                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5830                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5831                      if(appData.checkMates) {
5832                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5833                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5834                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
5835                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
5836                          return;
5837                      }
5838                 }
5839
5840                 /* Bare King in Shatranj (loses) or Losers (wins) */
5841                 if( NrW == 1 || NrPieces - NrW == 1) {
5842                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5843                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
5844                      if(appData.checkMates) {
5845                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5846                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5847                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5848                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5849                          return;
5850                      }
5851                   } else
5852                   if( gameInfo.variant == VariantShatranj && --bare < 0)
5853                   {    /* bare King */
5854                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5855                         if(appData.checkMates) {
5856                             /* but only adjudicate if adjudication enabled */
5857                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5858                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5859                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
5860                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5861                             return;
5862                         }
5863                   }
5864                 } else bare = 1;
5865
5866
5867             // don't wait for engine to announce game end if we can judge ourselves
5868             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5869                                        castlingRights[forwardMostMove]) ) {
5870               case MT_CHECK:
5871                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5872                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
5873                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5874                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5875                             checkCnt++;
5876                         if(checkCnt >= 2) {
5877                             reason = "Xboard adjudication: 3rd check";
5878                             epStatus[forwardMostMove] = EP_CHECKMATE;
5879                             break;
5880                         }
5881                     }
5882                 }
5883               case MT_NONE:
5884               default:
5885                 break;
5886               case MT_STALEMATE:
5887               case MT_STAINMATE:
5888                 reason = "Xboard adjudication: Stalemate";
5889                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5890                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
5891                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5892                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
5893                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5894                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5895                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5896                                                                         EP_CHECKMATE : EP_WINS);
5897                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5898                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5899                 }
5900                 break;
5901               case MT_CHECKMATE:
5902                 reason = "Xboard adjudication: Checkmate";
5903                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5904                 break;
5905             }
5906
5907                 switch(i = epStatus[forwardMostMove]) {
5908                     case EP_STALEMATE:
5909                         result = GameIsDrawn; break;
5910                     case EP_CHECKMATE:
5911                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5912                     case EP_WINS:
5913                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5914                     default:
5915                         result = (ChessMove) 0;
5916                 }
5917                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5918                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5919                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5920                     GameEnds( result, reason, GE_XBOARD );
5921                     return;
5922                 }
5923
5924                 /* Next absolutely insufficient mating material. */
5925                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
5926                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5927                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5928                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5929                 {    /* KBK, KNK, KK of KBKB with like Bishops */
5930
5931                      /* always flag draws, for judging claims */
5932                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
5933
5934                      if(appData.materialDraws) {
5935                          /* but only adjudicate them if adjudication enabled */
5936                          SendToProgram("force\n", cps->other); // suppress reply
5937                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5938                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5939                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5940                          return;
5941                      }
5942                 }
5943
5944                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5945                 if(NrPieces == 4 && 
5946                    (   NrWR == 1 && NrBR == 1 /* KRKR */
5947                    || NrWQ==1 && NrBQ==1     /* KQKQ */
5948                    || NrWN==2 || NrBN==2     /* KNNK */
5949                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5950                   ) ) {
5951                      if(--moveCount < 0 && appData.trivialDraws)
5952                      {    /* if the first 3 moves do not show a tactical win, declare draw */
5953                           SendToProgram("force\n", cps->other); // suppress reply
5954                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5955                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5956                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5957                           return;
5958                      }
5959                 } else moveCount = 6;
5960             }
5961           }
5962 #if 1
5963     if (appData.debugMode) { int i;
5964       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5965               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5966               appData.drawRepeats);
5967       for( i=forwardMostMove; i>=backwardMostMove; i-- )
5968            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5969
5970     }
5971 #endif
5972                 /* Check for rep-draws */
5973                 count = 0;
5974                 for(k = forwardMostMove-2;
5975                     k>=backwardMostMove && k>=forwardMostMove-100 &&
5976                         epStatus[k] < EP_UNKNOWN &&
5977                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5978                     k-=2)
5979                 {   int rights=0;
5980 #if 0
5981     if (appData.debugMode) {
5982       fprintf(debugFP, " loop\n");
5983     }
5984 #endif
5985                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
5986 #if 0
5987     if (appData.debugMode) {
5988       fprintf(debugFP, "match\n");
5989     }
5990 #endif
5991                         /* compare castling rights */
5992                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5993                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5994                                 rights++; /* King lost rights, while rook still had them */
5995                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5996                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5997                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5998                                    rights++; /* but at least one rook lost them */
5999                         }
6000                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6001                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6002                                 rights++; 
6003                         if( castlingRights[forwardMostMove][5] >= 0 ) {
6004                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6005                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6006                                    rights++;
6007                         }
6008 #if 0
6009     if (appData.debugMode) {
6010       for(i=0; i<nrCastlingRights; i++)
6011       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
6012     }
6013
6014     if (appData.debugMode) {
6015       fprintf(debugFP, " %d %d\n", rights, k);
6016     }
6017 #endif
6018                         if( rights == 0 && ++count > appData.drawRepeats-2
6019                             && appData.drawRepeats > 1) {
6020                              /* adjudicate after user-specified nr of repeats */
6021                              SendToProgram("force\n", cps->other); // suppress reply
6022                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6023                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6024                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6025                                 // [HGM] xiangqi: check for forbidden perpetuals
6026                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6027                                 for(m=forwardMostMove; m>k; m-=2) {
6028                                     if(MateTest(boards[m], PosFlags(m), 
6029                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
6030                                         ourPerpetual = 0; // the current mover did not always check
6031                                     if(MateTest(boards[m-1], PosFlags(m-1), 
6032                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
6033                                         hisPerpetual = 0; // the opponent did not always check
6034                                 }
6035                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6036                                                                         ourPerpetual, hisPerpetual);
6037                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6038                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6039                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6040                                     return;
6041                                 }
6042                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6043                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6044                                 // Now check for perpetual chases
6045                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6046                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6047                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6048                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6049                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6050                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6051                                         return;
6052                                     }
6053                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6054                                         break; // Abort repetition-checking loop.
6055                                 }
6056                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6057                              }
6058                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6059                              return;
6060                         }
6061                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6062                              epStatus[forwardMostMove] = EP_REP_DRAW;
6063                     }
6064                 }
6065
6066                 /* Now we test for 50-move draws. Determine ply count */
6067                 count = forwardMostMove;
6068                 /* look for last irreversble move */
6069                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6070                     count--;
6071                 /* if we hit starting position, add initial plies */
6072                 if( count == backwardMostMove )
6073                     count -= initialRulePlies;
6074                 count = forwardMostMove - count; 
6075                 if( count >= 100)
6076                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6077                          /* this is used to judge if draw claims are legal */
6078                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6079                          SendToProgram("force\n", cps->other); // suppress reply
6080                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6081                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6082                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6083                          return;
6084                 }
6085
6086                 /* if draw offer is pending, treat it as a draw claim
6087                  * when draw condition present, to allow engines a way to
6088                  * claim draws before making their move to avoid a race
6089                  * condition occurring after their move
6090                  */
6091                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6092                          char *p = NULL;
6093                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6094                              p = "Draw claim: 50-move rule";
6095                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6096                              p = "Draw claim: 3-fold repetition";
6097                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6098                              p = "Draw claim: insufficient mating material";
6099                          if( p != NULL ) {
6100                              SendToProgram("force\n", cps->other); // suppress reply
6101                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6102                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6103                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6104                              return;
6105                          }
6106                 }
6107
6108
6109                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6110                     SendToProgram("force\n", cps->other); // suppress reply
6111                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6112                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6113
6114                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6115
6116                     return;
6117                 }
6118         }
6119
6120         bookHit = NULL;
6121         if (gameMode == TwoMachinesPlay) {
6122             /* [HGM] relaying draw offers moved to after reception of move */
6123             /* and interpreting offer as claim if it brings draw condition */
6124             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6125                 SendToProgram("draw\n", cps->other);
6126             }
6127             if (cps->other->sendTime) {
6128                 SendTimeRemaining(cps->other,
6129                                   cps->other->twoMachinesColor[0] == 'w');
6130             }
6131             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6132             if (firstMove && !bookHit) {
6133                 firstMove = FALSE;
6134                 if (cps->other->useColors) {
6135                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6136                 }
6137                 SendToProgram("go\n", cps->other);
6138             }
6139             cps->other->maybeThinking = TRUE;
6140         }
6141
6142         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6143         
6144         if (!pausing && appData.ringBellAfterMoves) {
6145             RingBell();
6146         }
6147
6148         /* 
6149          * Reenable menu items that were disabled while
6150          * machine was thinking
6151          */
6152         if (gameMode != TwoMachinesPlay)
6153             SetUserThinkingEnables();
6154
6155         // [HGM] book: after book hit opponent has received move and is now in force mode
6156         // force the book reply into it, and then fake that it outputted this move by jumping
6157         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6158         if(bookHit) {
6159                 static char bookMove[MSG_SIZ]; // a bit generous?
6160
6161                 strcpy(bookMove, "move ");
6162                 strcat(bookMove, bookHit);
6163                 message = bookMove;
6164                 cps = cps->other;
6165                 programStats.nodes = programStats.depth = programStats.time = 
6166                 programStats.score = programStats.got_only_move = 0;
6167                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6168
6169                 if(cps->lastPing != cps->lastPong) {
6170                     savedMessage = message; // args for deferred call
6171                     savedState = cps;
6172                     ScheduleDelayedEvent(DeferredBookMove, 10);
6173                     return;
6174                 }
6175                 goto FakeBookMove;
6176         }
6177
6178         return;
6179     }
6180
6181     /* Set special modes for chess engines.  Later something general
6182      *  could be added here; for now there is just one kludge feature,
6183      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6184      *  when "xboard" is given as an interactive command.
6185      */
6186     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6187         cps->useSigint = FALSE;
6188         cps->useSigterm = FALSE;
6189     }
6190     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6191       ParseFeatures(message+8, cps);
6192       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6193     }
6194
6195     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6196      * want this, I was asked to put it in, and obliged.
6197      */
6198     if (!strncmp(message, "setboard ", 9)) {
6199         Board initial_position; int i;
6200
6201         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6202
6203         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6204             DisplayError(_("Bad FEN received from engine"), 0);
6205             return ;
6206         } else {
6207            Reset(FALSE, FALSE);
6208            CopyBoard(boards[0], initial_position);
6209            initialRulePlies = FENrulePlies;
6210            epStatus[0] = FENepStatus;
6211            for( i=0; i<nrCastlingRights; i++ )
6212                 castlingRights[0][i] = FENcastlingRights[i];
6213            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6214            else gameMode = MachinePlaysBlack;                 
6215            DrawPosition(FALSE, boards[currentMove]);
6216         }
6217         return;
6218     }
6219
6220     /*
6221      * Look for communication commands
6222      */
6223     if (!strncmp(message, "telluser ", 9)) {
6224         DisplayNote(message + 9);
6225         return;
6226     }
6227     if (!strncmp(message, "tellusererror ", 14)) {
6228         DisplayError(message + 14, 0);
6229         return;
6230     }
6231     if (!strncmp(message, "tellopponent ", 13)) {
6232       if (appData.icsActive) {
6233         if (loggedOn) {
6234           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6235           SendToICS(buf1);
6236         }
6237       } else {
6238         DisplayNote(message + 13);
6239       }
6240       return;
6241     }
6242     if (!strncmp(message, "tellothers ", 11)) {
6243       if (appData.icsActive) {
6244         if (loggedOn) {
6245           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6246           SendToICS(buf1);
6247         }
6248       }
6249       return;
6250     }
6251     if (!strncmp(message, "tellall ", 8)) {
6252       if (appData.icsActive) {
6253         if (loggedOn) {
6254           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6255           SendToICS(buf1);
6256         }
6257       } else {
6258         DisplayNote(message + 8);
6259       }
6260       return;
6261     }
6262     if (strncmp(message, "warning", 7) == 0) {
6263         /* Undocumented feature, use tellusererror in new code */
6264         DisplayError(message, 0);
6265         return;
6266     }
6267     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6268         strcpy(realname, cps->tidy);
6269         strcat(realname, " query");
6270         AskQuestion(realname, buf2, buf1, cps->pr);
6271         return;
6272     }
6273     /* Commands from the engine directly to ICS.  We don't allow these to be 
6274      *  sent until we are logged on. Crafty kibitzes have been known to 
6275      *  interfere with the login process.
6276      */
6277     if (loggedOn) {
6278         if (!strncmp(message, "tellics ", 8)) {
6279             SendToICS(message + 8);
6280             SendToICS("\n");
6281             return;
6282         }
6283         if (!strncmp(message, "tellicsnoalias ", 15)) {
6284             SendToICS(ics_prefix);
6285             SendToICS(message + 15);
6286             SendToICS("\n");
6287             return;
6288         }
6289         /* The following are for backward compatibility only */
6290         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6291             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6292             SendToICS(ics_prefix);
6293             SendToICS(message);
6294             SendToICS("\n");
6295             return;
6296         }
6297     }
6298     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6299         return;
6300     }
6301     /*
6302      * If the move is illegal, cancel it and redraw the board.
6303      * Also deal with other error cases.  Matching is rather loose
6304      * here to accommodate engines written before the spec.
6305      */
6306     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6307         strncmp(message, "Error", 5) == 0) {
6308         if (StrStr(message, "name") || 
6309             StrStr(message, "rating") || StrStr(message, "?") ||
6310             StrStr(message, "result") || StrStr(message, "board") ||
6311             StrStr(message, "bk") || StrStr(message, "computer") ||
6312             StrStr(message, "variant") || StrStr(message, "hint") ||
6313             StrStr(message, "random") || StrStr(message, "depth") ||
6314             StrStr(message, "accepted")) {
6315             return;
6316         }
6317         if (StrStr(message, "protover")) {
6318           /* Program is responding to input, so it's apparently done
6319              initializing, and this error message indicates it is
6320              protocol version 1.  So we don't need to wait any longer
6321              for it to initialize and send feature commands. */
6322           FeatureDone(cps, 1);
6323           cps->protocolVersion = 1;
6324           return;
6325         }
6326         cps->maybeThinking = FALSE;
6327
6328         if (StrStr(message, "draw")) {
6329             /* Program doesn't have "draw" command */
6330             cps->sendDrawOffers = 0;
6331             return;
6332         }
6333         if (cps->sendTime != 1 &&
6334             (StrStr(message, "time") || StrStr(message, "otim"))) {
6335           /* Program apparently doesn't have "time" or "otim" command */
6336           cps->sendTime = 0;
6337           return;
6338         }
6339         if (StrStr(message, "analyze")) {
6340             cps->analysisSupport = FALSE;
6341             cps->analyzing = FALSE;
6342             Reset(FALSE, TRUE);
6343             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6344             DisplayError(buf2, 0);
6345             return;
6346         }
6347         if (StrStr(message, "(no matching move)st")) {
6348           /* Special kludge for GNU Chess 4 only */
6349           cps->stKludge = TRUE;
6350           SendTimeControl(cps, movesPerSession, timeControl,
6351                           timeIncrement, appData.searchDepth,
6352                           searchTime);
6353           return;
6354         }
6355         if (StrStr(message, "(no matching move)sd")) {
6356           /* Special kludge for GNU Chess 4 only */
6357           cps->sdKludge = TRUE;
6358           SendTimeControl(cps, movesPerSession, timeControl,
6359                           timeIncrement, appData.searchDepth,
6360                           searchTime);
6361           return;
6362         }
6363         if (!StrStr(message, "llegal")) {
6364             return;
6365         }
6366         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6367             gameMode == IcsIdle) return;
6368         if (forwardMostMove <= backwardMostMove) return;
6369 #if 0
6370         /* Following removed: it caused a bug where a real illegal move
6371            message in analyze mored would be ignored. */
6372         if (cps == &first && programStats.ok_to_send == 0) {
6373             /* Bogus message from Crafty responding to "."  This filtering
6374                can miss some of the bad messages, but fortunately the bug 
6375                is fixed in current Crafty versions, so it doesn't matter. */
6376             return;
6377         }
6378 #endif
6379         if (pausing) PauseEvent();
6380         if (gameMode == PlayFromGameFile) {
6381             /* Stop reading this game file */
6382             gameMode = EditGame;
6383             ModeHighlight();
6384         }
6385         currentMove = --forwardMostMove;
6386         DisplayMove(currentMove-1); /* before DisplayMoveError */
6387         SwitchClocks();
6388         DisplayBothClocks();
6389         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6390                 parseList[currentMove], cps->which);
6391         DisplayMoveError(buf1);
6392         DrawPosition(FALSE, boards[currentMove]);
6393
6394         /* [HGM] illegal-move claim should forfeit game when Xboard */
6395         /* only passes fully legal moves                            */
6396         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6397             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6398                                 "False illegal-move claim", GE_XBOARD );
6399         }
6400         return;
6401     }
6402     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6403         /* Program has a broken "time" command that
6404            outputs a string not ending in newline.
6405            Don't use it. */
6406         cps->sendTime = 0;
6407     }
6408     
6409     /*
6410      * If chess program startup fails, exit with an error message.
6411      * Attempts to recover here are futile.
6412      */
6413     if ((StrStr(message, "unknown host") != NULL)
6414         || (StrStr(message, "No remote directory") != NULL)
6415         || (StrStr(message, "not found") != NULL)
6416         || (StrStr(message, "No such file") != NULL)
6417         || (StrStr(message, "can't alloc") != NULL)
6418         || (StrStr(message, "Permission denied") != NULL)) {
6419
6420         cps->maybeThinking = FALSE;
6421         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6422                 cps->which, cps->program, cps->host, message);
6423         RemoveInputSource(cps->isr);
6424         DisplayFatalError(buf1, 0, 1);
6425         return;
6426     }
6427     
6428     /* 
6429      * Look for hint output
6430      */
6431     if (sscanf(message, "Hint: %s", buf1) == 1) {
6432         if (cps == &first && hintRequested) {
6433             hintRequested = FALSE;
6434             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6435                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6436                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6437                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6438                                     fromY, fromX, toY, toX, promoChar, buf1);
6439                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6440                 DisplayInformation(buf2);
6441             } else {
6442                 /* Hint move could not be parsed!? */
6443               snprintf(buf2, sizeof(buf2),
6444                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6445                         buf1, cps->which);
6446                 DisplayError(buf2, 0);
6447             }
6448         } else {
6449             strcpy(lastHint, buf1);
6450         }
6451         return;
6452     }
6453
6454     /*
6455      * Ignore other messages if game is not in progress
6456      */
6457     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6458         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6459
6460     /*
6461      * look for win, lose, draw, or draw offer
6462      */
6463     if (strncmp(message, "1-0", 3) == 0) {
6464         char *p, *q, *r = "";
6465         p = strchr(message, '{');
6466         if (p) {
6467             q = strchr(p, '}');
6468             if (q) {
6469                 *q = NULLCHAR;
6470                 r = p + 1;
6471             }
6472         }
6473         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6474         return;
6475     } else if (strncmp(message, "0-1", 3) == 0) {
6476         char *p, *q, *r = "";
6477         p = strchr(message, '{');
6478         if (p) {
6479             q = strchr(p, '}');
6480             if (q) {
6481                 *q = NULLCHAR;
6482                 r = p + 1;
6483             }
6484         }
6485         /* Kludge for Arasan 4.1 bug */
6486         if (strcmp(r, "Black resigns") == 0) {
6487             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6488             return;
6489         }
6490         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6491         return;
6492     } else if (strncmp(message, "1/2", 3) == 0) {
6493         char *p, *q, *r = "";
6494         p = strchr(message, '{');
6495         if (p) {
6496             q = strchr(p, '}');
6497             if (q) {
6498                 *q = NULLCHAR;
6499                 r = p + 1;
6500             }
6501         }
6502             
6503         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6504         return;
6505
6506     } else if (strncmp(message, "White resign", 12) == 0) {
6507         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6508         return;
6509     } else if (strncmp(message, "Black resign", 12) == 0) {
6510         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6511         return;
6512     } else if (strncmp(message, "White matches", 13) == 0 ||
6513                strncmp(message, "Black matches", 13) == 0   ) {
6514         /* [HGM] ignore GNUShogi noises */
6515         return;
6516     } else if (strncmp(message, "White", 5) == 0 &&
6517                message[5] != '(' &&
6518                StrStr(message, "Black") == NULL) {
6519         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6520         return;
6521     } else if (strncmp(message, "Black", 5) == 0 &&
6522                message[5] != '(') {
6523         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6524         return;
6525     } else if (strcmp(message, "resign") == 0 ||
6526                strcmp(message, "computer resigns") == 0) {
6527         switch (gameMode) {
6528           case MachinePlaysBlack:
6529           case IcsPlayingBlack:
6530             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6531             break;
6532           case MachinePlaysWhite:
6533           case IcsPlayingWhite:
6534             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6535             break;
6536           case TwoMachinesPlay:
6537             if (cps->twoMachinesColor[0] == 'w')
6538               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6539             else
6540               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6541             break;
6542           default:
6543             /* can't happen */
6544             break;
6545         }
6546         return;
6547     } else if (strncmp(message, "opponent mates", 14) == 0) {
6548         switch (gameMode) {
6549           case MachinePlaysBlack:
6550           case IcsPlayingBlack:
6551             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6552             break;
6553           case MachinePlaysWhite:
6554           case IcsPlayingWhite:
6555             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6556             break;
6557           case TwoMachinesPlay:
6558             if (cps->twoMachinesColor[0] == 'w')
6559               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6560             else
6561               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6562             break;
6563           default:
6564             /* can't happen */
6565             break;
6566         }
6567         return;
6568     } else if (strncmp(message, "computer mates", 14) == 0) {
6569         switch (gameMode) {
6570           case MachinePlaysBlack:
6571           case IcsPlayingBlack:
6572             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6573             break;
6574           case MachinePlaysWhite:
6575           case IcsPlayingWhite:
6576             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6577             break;
6578           case TwoMachinesPlay:
6579             if (cps->twoMachinesColor[0] == 'w')
6580               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6581             else
6582               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6583             break;
6584           default:
6585             /* can't happen */
6586             break;
6587         }
6588         return;
6589     } else if (strncmp(message, "checkmate", 9) == 0) {
6590         if (WhiteOnMove(forwardMostMove)) {
6591             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6592         } else {
6593             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6594         }
6595         return;
6596     } else if (strstr(message, "Draw") != NULL ||
6597                strstr(message, "game is a draw") != NULL) {
6598         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6599         return;
6600     } else if (strstr(message, "offer") != NULL &&
6601                strstr(message, "draw") != NULL) {
6602 #if ZIPPY
6603         if (appData.zippyPlay && first.initDone) {
6604             /* Relay offer to ICS */
6605             SendToICS(ics_prefix);
6606             SendToICS("draw\n");
6607         }
6608 #endif
6609         cps->offeredDraw = 2; /* valid until this engine moves twice */
6610         if (gameMode == TwoMachinesPlay) {
6611             if (cps->other->offeredDraw) {
6612                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6613             /* [HGM] in two-machine mode we delay relaying draw offer      */
6614             /* until after we also have move, to see if it is really claim */
6615             }
6616 #if 0
6617               else {
6618                 if (cps->other->sendDrawOffers) {
6619                     SendToProgram("draw\n", cps->other);
6620                 }
6621             }
6622 #endif
6623         } else if (gameMode == MachinePlaysWhite ||
6624                    gameMode == MachinePlaysBlack) {
6625           if (userOfferedDraw) {
6626             DisplayInformation(_("Machine accepts your draw offer"));
6627             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6628           } else {
6629             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6630           }
6631         }
6632     }
6633
6634     
6635     /*
6636      * Look for thinking output
6637      */
6638     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6639           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6640                                 ) {
6641         int plylev, mvleft, mvtot, curscore, time;
6642         char mvname[MOVE_LEN];
6643         u64 nodes; // [DM]
6644         char plyext;
6645         int ignore = FALSE;
6646         int prefixHint = FALSE;
6647         mvname[0] = NULLCHAR;
6648
6649         switch (gameMode) {
6650           case MachinePlaysBlack:
6651           case IcsPlayingBlack:
6652             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6653             break;
6654           case MachinePlaysWhite:
6655           case IcsPlayingWhite:
6656             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6657             break;
6658           case AnalyzeMode:
6659           case AnalyzeFile:
6660             break;
6661           case IcsObserving: /* [DM] icsEngineAnalyze */
6662             if (!appData.icsEngineAnalyze) ignore = TRUE;
6663             break;
6664           case TwoMachinesPlay:
6665             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6666                 ignore = TRUE;
6667             }
6668             break;
6669           default:
6670             ignore = TRUE;
6671             break;
6672         }
6673
6674         if (!ignore) {
6675             buf1[0] = NULLCHAR;
6676             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6677                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6678
6679                 if (plyext != ' ' && plyext != '\t') {
6680                     time *= 100;
6681                 }
6682
6683                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6684                 if( cps->scoreIsAbsolute && 
6685                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6686                 {
6687                     curscore = -curscore;
6688                 }
6689
6690
6691                 programStats.depth = plylev;
6692                 programStats.nodes = nodes;
6693                 programStats.time = time;
6694                 programStats.score = curscore;
6695                 programStats.got_only_move = 0;
6696
6697                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6698                         int ticklen;
6699
6700                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6701                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6702                         if(WhiteOnMove(forwardMostMove)) 
6703                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6704                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6705                 }
6706
6707                 /* Buffer overflow protection */
6708                 if (buf1[0] != NULLCHAR) {
6709                     if (strlen(buf1) >= sizeof(programStats.movelist)
6710                         && appData.debugMode) {
6711                         fprintf(debugFP,
6712                                 "PV is too long; using the first %d bytes.\n",
6713                                 sizeof(programStats.movelist) - 1);
6714                     }
6715
6716                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6717                 } else {
6718                     sprintf(programStats.movelist, " no PV\n");
6719                 }
6720
6721                 if (programStats.seen_stat) {
6722                     programStats.ok_to_send = 1;
6723                 }
6724
6725                 if (strchr(programStats.movelist, '(') != NULL) {
6726                     programStats.line_is_book = 1;
6727                     programStats.nr_moves = 0;
6728                     programStats.moves_left = 0;
6729                 } else {
6730                     programStats.line_is_book = 0;
6731                 }
6732
6733                 SendProgramStatsToFrontend( cps, &programStats );
6734
6735                 /* 
6736                     [AS] Protect the thinkOutput buffer from overflow... this
6737                     is only useful if buf1 hasn't overflowed first!
6738                 */
6739                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6740                         plylev, 
6741                         (gameMode == TwoMachinesPlay ?
6742                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6743                         ((double) curscore) / 100.0,
6744                         prefixHint ? lastHint : "",
6745                         prefixHint ? " " : "" );
6746
6747                 if( buf1[0] != NULLCHAR ) {
6748                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6749
6750                     if( strlen(buf1) > max_len ) {
6751                         if( appData.debugMode) {
6752                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6753                         }
6754                         buf1[max_len+1] = '\0';
6755                     }
6756
6757                     strcat( thinkOutput, buf1 );
6758                 }
6759
6760                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6761                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6762                     DisplayMove(currentMove - 1);
6763                     DisplayAnalysis();
6764                 }
6765                 return;
6766
6767             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6768                 /* crafty (9.25+) says "(only move) <move>"
6769                  * if there is only 1 legal move
6770                  */
6771                 sscanf(p, "(only move) %s", buf1);
6772                 sprintf(thinkOutput, "%s (only move)", buf1);
6773                 sprintf(programStats.movelist, "%s (only move)", buf1);
6774                 programStats.depth = 1;
6775                 programStats.nr_moves = 1;
6776                 programStats.moves_left = 1;
6777                 programStats.nodes = 1;
6778                 programStats.time = 1;
6779                 programStats.got_only_move = 1;
6780
6781                 /* Not really, but we also use this member to
6782                    mean "line isn't going to change" (Crafty
6783                    isn't searching, so stats won't change) */
6784                 programStats.line_is_book = 1;
6785
6786                 SendProgramStatsToFrontend( cps, &programStats );
6787                 
6788                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
6789                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6790                     DisplayMove(currentMove - 1);
6791                     DisplayAnalysis();
6792                 }
6793                 return;
6794             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6795                               &time, &nodes, &plylev, &mvleft,
6796                               &mvtot, mvname) >= 5) {
6797                 /* The stat01: line is from Crafty (9.29+) in response
6798                    to the "." command */
6799                 programStats.seen_stat = 1;
6800                 cps->maybeThinking = TRUE;
6801
6802                 if (programStats.got_only_move || !appData.periodicUpdates)
6803                   return;
6804
6805                 programStats.depth = plylev;
6806                 programStats.time = time;
6807                 programStats.nodes = nodes;
6808                 programStats.moves_left = mvleft;
6809                 programStats.nr_moves = mvtot;
6810                 strcpy(programStats.move_name, mvname);
6811                 programStats.ok_to_send = 1;
6812                 programStats.movelist[0] = '\0';
6813
6814                 SendProgramStatsToFrontend( cps, &programStats );
6815
6816                 DisplayAnalysis();
6817                 return;
6818
6819             } else if (strncmp(message,"++",2) == 0) {
6820                 /* Crafty 9.29+ outputs this */
6821                 programStats.got_fail = 2;
6822                 return;
6823
6824             } else if (strncmp(message,"--",2) == 0) {
6825                 /* Crafty 9.29+ outputs this */
6826                 programStats.got_fail = 1;
6827                 return;
6828
6829             } else if (thinkOutput[0] != NULLCHAR &&
6830                        strncmp(message, "    ", 4) == 0) {
6831                 unsigned message_len;
6832
6833                 p = message;
6834                 while (*p && *p == ' ') p++;
6835
6836                 message_len = strlen( p );
6837
6838                 /* [AS] Avoid buffer overflow */
6839                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6840                     strcat(thinkOutput, " ");
6841                     strcat(thinkOutput, p);
6842                 }
6843
6844                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6845                     strcat(programStats.movelist, " ");
6846                     strcat(programStats.movelist, p);
6847                 }
6848
6849                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6850                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6851                     DisplayMove(currentMove - 1);
6852                     DisplayAnalysis();
6853                 }
6854                 return;
6855             }
6856         }
6857         else {
6858             buf1[0] = NULLCHAR;
6859
6860             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6861                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
6862             {
6863                 ChessProgramStats cpstats;
6864
6865                 if (plyext != ' ' && plyext != '\t') {
6866                     time *= 100;
6867                 }
6868
6869                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6870                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6871                     curscore = -curscore;
6872                 }
6873
6874                 cpstats.depth = plylev;
6875                 cpstats.nodes = nodes;
6876                 cpstats.time = time;
6877                 cpstats.score = curscore;
6878                 cpstats.got_only_move = 0;
6879                 cpstats.movelist[0] = '\0';
6880
6881                 if (buf1[0] != NULLCHAR) {
6882                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6883                 }
6884
6885                 cpstats.ok_to_send = 0;
6886                 cpstats.line_is_book = 0;
6887                 cpstats.nr_moves = 0;
6888                 cpstats.moves_left = 0;
6889
6890                 SendProgramStatsToFrontend( cps, &cpstats );
6891             }
6892         }
6893     }
6894 }
6895
6896
6897 /* Parse a game score from the character string "game", and
6898    record it as the history of the current game.  The game
6899    score is NOT assumed to start from the standard position. 
6900    The display is not updated in any way.
6901    */
6902 void
6903 ParseGameHistory(game)
6904      char *game;
6905 {
6906     ChessMove moveType;
6907     int fromX, fromY, toX, toY, boardIndex;
6908     char promoChar;
6909     char *p, *q;
6910     char buf[MSG_SIZ];
6911
6912     if (appData.debugMode)
6913       fprintf(debugFP, "Parsing game history: %s\n", game);
6914
6915     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6916     gameInfo.site = StrSave(appData.icsHost);
6917     gameInfo.date = PGNDate();
6918     gameInfo.round = StrSave("-");
6919
6920     /* Parse out names of players */
6921     while (*game == ' ') game++;
6922     p = buf;
6923     while (*game != ' ') *p++ = *game++;
6924     *p = NULLCHAR;
6925     gameInfo.white = StrSave(buf);
6926     while (*game == ' ') game++;
6927     p = buf;
6928     while (*game != ' ' && *game != '\n') *p++ = *game++;
6929     *p = NULLCHAR;
6930     gameInfo.black = StrSave(buf);
6931
6932     /* Parse moves */
6933     boardIndex = blackPlaysFirst ? 1 : 0;
6934     yynewstr(game);
6935     for (;;) {
6936         yyboardindex = boardIndex;
6937         moveType = (ChessMove) yylex();
6938         switch (moveType) {
6939           case IllegalMove:             /* maybe suicide chess, etc. */
6940   if (appData.debugMode) {
6941     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6942     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6943     setbuf(debugFP, NULL);
6944   }
6945           case WhitePromotionChancellor:
6946           case BlackPromotionChancellor:
6947           case WhitePromotionArchbishop:
6948           case BlackPromotionArchbishop:
6949           case WhitePromotionQueen:
6950           case BlackPromotionQueen:
6951           case WhitePromotionRook:
6952           case BlackPromotionRook:
6953           case WhitePromotionBishop:
6954           case BlackPromotionBishop:
6955           case WhitePromotionKnight:
6956           case BlackPromotionKnight:
6957           case WhitePromotionKing:
6958           case BlackPromotionKing:
6959           case NormalMove:
6960           case WhiteCapturesEnPassant:
6961           case BlackCapturesEnPassant:
6962           case WhiteKingSideCastle:
6963           case WhiteQueenSideCastle:
6964           case BlackKingSideCastle:
6965           case BlackQueenSideCastle:
6966           case WhiteKingSideCastleWild:
6967           case WhiteQueenSideCastleWild:
6968           case BlackKingSideCastleWild:
6969           case BlackQueenSideCastleWild:
6970           /* PUSH Fabien */
6971           case WhiteHSideCastleFR:
6972           case WhiteASideCastleFR:
6973           case BlackHSideCastleFR:
6974           case BlackASideCastleFR:
6975           /* POP Fabien */
6976             fromX = currentMoveString[0] - AAA;
6977             fromY = currentMoveString[1] - ONE;
6978             toX = currentMoveString[2] - AAA;
6979             toY = currentMoveString[3] - ONE;
6980             promoChar = currentMoveString[4];
6981             break;
6982           case WhiteDrop:
6983           case BlackDrop:
6984             fromX = moveType == WhiteDrop ?
6985               (int) CharToPiece(ToUpper(currentMoveString[0])) :
6986             (int) CharToPiece(ToLower(currentMoveString[0]));
6987             fromY = DROP_RANK;
6988             toX = currentMoveString[2] - AAA;
6989             toY = currentMoveString[3] - ONE;
6990             promoChar = NULLCHAR;
6991             break;
6992           case AmbiguousMove:
6993             /* bug? */
6994             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6995   if (appData.debugMode) {
6996     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6997     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6998     setbuf(debugFP, NULL);
6999   }
7000             DisplayError(buf, 0);
7001             return;
7002           case ImpossibleMove:
7003             /* bug? */
7004             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7005   if (appData.debugMode) {
7006     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7007     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7008     setbuf(debugFP, NULL);
7009   }
7010             DisplayError(buf, 0);
7011             return;
7012           case (ChessMove) 0:   /* end of file */
7013             if (boardIndex < backwardMostMove) {
7014                 /* Oops, gap.  How did that happen? */
7015                 DisplayError(_("Gap in move list"), 0);
7016                 return;
7017             }
7018             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7019             if (boardIndex > forwardMostMove) {
7020                 forwardMostMove = boardIndex;
7021             }
7022             return;
7023           case ElapsedTime:
7024             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7025                 strcat(parseList[boardIndex-1], " ");
7026                 strcat(parseList[boardIndex-1], yy_text);
7027             }
7028             continue;
7029           case Comment:
7030           case PGNTag:
7031           case NAG:
7032           default:
7033             /* ignore */
7034             continue;
7035           case WhiteWins:
7036           case BlackWins:
7037           case GameIsDrawn:
7038           case GameUnfinished:
7039             if (gameMode == IcsExamining) {
7040                 if (boardIndex < backwardMostMove) {
7041                     /* Oops, gap.  How did that happen? */
7042                     return;
7043                 }
7044                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7045                 return;
7046             }
7047             gameInfo.result = moveType;
7048             p = strchr(yy_text, '{');
7049             if (p == NULL) p = strchr(yy_text, '(');
7050             if (p == NULL) {
7051                 p = yy_text;
7052                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7053             } else {
7054                 q = strchr(p, *p == '{' ? '}' : ')');
7055                 if (q != NULL) *q = NULLCHAR;
7056                 p++;
7057             }
7058             gameInfo.resultDetails = StrSave(p);
7059             continue;
7060         }
7061         if (boardIndex >= forwardMostMove &&
7062             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7063             backwardMostMove = blackPlaysFirst ? 1 : 0;
7064             return;
7065         }
7066         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7067                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7068                                  parseList[boardIndex]);
7069         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7070         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7071         /* currentMoveString is set as a side-effect of yylex */
7072         strcpy(moveList[boardIndex], currentMoveString);
7073         strcat(moveList[boardIndex], "\n");
7074         boardIndex++;
7075         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7076                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7077         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7078                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7079           case MT_NONE:
7080           case MT_STALEMATE:
7081           default:
7082             break;
7083           case MT_CHECK:
7084             if(gameInfo.variant != VariantShogi)
7085                 strcat(parseList[boardIndex - 1], "+");
7086             break;
7087           case MT_CHECKMATE:
7088           case MT_STAINMATE:
7089             strcat(parseList[boardIndex - 1], "#");
7090             break;
7091         }
7092     }
7093 }
7094
7095
7096 /* Apply a move to the given board  */
7097 void
7098 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7099      int fromX, fromY, toX, toY;
7100      int promoChar;
7101      Board board;
7102      char *castling;
7103      char *ep;
7104 {
7105   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7106
7107     /* [HGM] compute & store e.p. status and castling rights for new position */
7108     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7109     { int i;
7110
7111       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7112       oldEP = *ep;
7113       *ep = EP_NONE;
7114
7115       if( board[toY][toX] != EmptySquare ) 
7116            *ep = EP_CAPTURE;  
7117
7118       if( board[fromY][fromX] == WhitePawn ) {
7119            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7120                *ep = EP_PAWN_MOVE;
7121            if( toY-fromY==2) {
7122                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7123                         gameInfo.variant != VariantBerolina || toX < fromX)
7124                       *ep = toX | berolina;
7125                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7126                         gameInfo.variant != VariantBerolina || toX > fromX) 
7127                       *ep = toX;
7128            }
7129       } else 
7130       if( board[fromY][fromX] == BlackPawn ) {
7131            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7132                *ep = EP_PAWN_MOVE; 
7133            if( toY-fromY== -2) {
7134                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7135                         gameInfo.variant != VariantBerolina || toX < fromX)
7136                       *ep = toX | berolina;
7137                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7138                         gameInfo.variant != VariantBerolina || toX > fromX) 
7139                       *ep = toX;
7140            }
7141        }
7142
7143        for(i=0; i<nrCastlingRights; i++) {
7144            if(castling[i] == fromX && castlingRank[i] == fromY ||
7145               castling[i] == toX   && castlingRank[i] == toY   
7146              ) castling[i] = -1; // revoke for moved or captured piece
7147        }
7148
7149     }
7150
7151   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7152   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7153        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7154          
7155   if (fromX == toX && fromY == toY) return;
7156
7157   if (fromY == DROP_RANK) {
7158         /* must be first */
7159         piece = board[toY][toX] = (ChessSquare) fromX;
7160   } else {
7161      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7162      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7163      if(gameInfo.variant == VariantKnightmate)
7164          king += (int) WhiteUnicorn - (int) WhiteKing;
7165
7166     /* Code added by Tord: */
7167     /* FRC castling assumed when king captures friendly rook. */
7168     if (board[fromY][fromX] == WhiteKing &&
7169              board[toY][toX] == WhiteRook) {
7170       board[fromY][fromX] = EmptySquare;
7171       board[toY][toX] = EmptySquare;
7172       if(toX > fromX) {
7173         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7174       } else {
7175         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7176       }
7177     } else if (board[fromY][fromX] == BlackKing &&
7178                board[toY][toX] == BlackRook) {
7179       board[fromY][fromX] = EmptySquare;
7180       board[toY][toX] = EmptySquare;
7181       if(toX > fromX) {
7182         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7183       } else {
7184         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7185       }
7186     /* End of code added by Tord */
7187
7188     } else if (board[fromY][fromX] == king
7189         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7190         && toY == fromY && toX > fromX+1) {
7191         board[fromY][fromX] = EmptySquare;
7192         board[toY][toX] = king;
7193         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7194         board[fromY][BOARD_RGHT-1] = EmptySquare;
7195     } else if (board[fromY][fromX] == king
7196         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7197                && toY == fromY && toX < fromX-1) {
7198         board[fromY][fromX] = EmptySquare;
7199         board[toY][toX] = king;
7200         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7201         board[fromY][BOARD_LEFT] = EmptySquare;
7202     } else if (board[fromY][fromX] == WhitePawn
7203                && toY == BOARD_HEIGHT-1
7204                && gameInfo.variant != VariantXiangqi
7205                ) {
7206         /* white pawn promotion */
7207         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7208         if (board[toY][toX] == EmptySquare) {
7209             board[toY][toX] = WhiteQueen;
7210         }
7211         if(gameInfo.variant==VariantBughouse ||
7212            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7213             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7214         board[fromY][fromX] = EmptySquare;
7215     } else if ((fromY == BOARD_HEIGHT-4)
7216                && (toX != fromX)
7217                && gameInfo.variant != VariantXiangqi
7218                && gameInfo.variant != VariantBerolina
7219                && (board[fromY][fromX] == WhitePawn)
7220                && (board[toY][toX] == EmptySquare)) {
7221         board[fromY][fromX] = EmptySquare;
7222         board[toY][toX] = WhitePawn;
7223         captured = board[toY - 1][toX];
7224         board[toY - 1][toX] = EmptySquare;
7225     } else if ((fromY == BOARD_HEIGHT-4)
7226                && (toX == fromX)
7227                && gameInfo.variant == VariantBerolina
7228                && (board[fromY][fromX] == WhitePawn)
7229                && (board[toY][toX] == EmptySquare)) {
7230         board[fromY][fromX] = EmptySquare;
7231         board[toY][toX] = WhitePawn;
7232         if(oldEP & EP_BEROLIN_A) {
7233                 captured = board[fromY][fromX-1];
7234                 board[fromY][fromX-1] = EmptySquare;
7235         }else{  captured = board[fromY][fromX+1];
7236                 board[fromY][fromX+1] = EmptySquare;
7237         }
7238     } else if (board[fromY][fromX] == king
7239         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7240                && toY == fromY && toX > fromX+1) {
7241         board[fromY][fromX] = EmptySquare;
7242         board[toY][toX] = king;
7243         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7244         board[fromY][BOARD_RGHT-1] = EmptySquare;
7245     } else if (board[fromY][fromX] == king
7246         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7247                && toY == fromY && toX < fromX-1) {
7248         board[fromY][fromX] = EmptySquare;
7249         board[toY][toX] = king;
7250         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7251         board[fromY][BOARD_LEFT] = EmptySquare;
7252     } else if (fromY == 7 && fromX == 3
7253                && board[fromY][fromX] == BlackKing
7254                && toY == 7 && toX == 5) {
7255         board[fromY][fromX] = EmptySquare;
7256         board[toY][toX] = BlackKing;
7257         board[fromY][7] = EmptySquare;
7258         board[toY][4] = BlackRook;
7259     } else if (fromY == 7 && fromX == 3
7260                && board[fromY][fromX] == BlackKing
7261                && toY == 7 && toX == 1) {
7262         board[fromY][fromX] = EmptySquare;
7263         board[toY][toX] = BlackKing;
7264         board[fromY][0] = EmptySquare;
7265         board[toY][2] = BlackRook;
7266     } else if (board[fromY][fromX] == BlackPawn
7267                && toY == 0
7268                && gameInfo.variant != VariantXiangqi
7269                ) {
7270         /* black pawn promotion */
7271         board[0][toX] = CharToPiece(ToLower(promoChar));
7272         if (board[0][toX] == EmptySquare) {
7273             board[0][toX] = BlackQueen;
7274         }
7275         if(gameInfo.variant==VariantBughouse ||
7276            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7277             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7278         board[fromY][fromX] = EmptySquare;
7279     } else if ((fromY == 3)
7280                && (toX != fromX)
7281                && gameInfo.variant != VariantXiangqi
7282                && gameInfo.variant != VariantBerolina
7283                && (board[fromY][fromX] == BlackPawn)
7284                && (board[toY][toX] == EmptySquare)) {
7285         board[fromY][fromX] = EmptySquare;
7286         board[toY][toX] = BlackPawn;
7287         captured = board[toY + 1][toX];
7288         board[toY + 1][toX] = EmptySquare;
7289     } else if ((fromY == 3)
7290                && (toX == fromX)
7291                && gameInfo.variant == VariantBerolina
7292                && (board[fromY][fromX] == BlackPawn)
7293                && (board[toY][toX] == EmptySquare)) {
7294         board[fromY][fromX] = EmptySquare;
7295         board[toY][toX] = BlackPawn;
7296         if(oldEP & EP_BEROLIN_A) {
7297                 captured = board[fromY][fromX-1];
7298                 board[fromY][fromX-1] = EmptySquare;
7299         }else{  captured = board[fromY][fromX+1];
7300                 board[fromY][fromX+1] = EmptySquare;
7301         }
7302     } else {
7303         board[toY][toX] = board[fromY][fromX];
7304         board[fromY][fromX] = EmptySquare;
7305     }
7306
7307     /* [HGM] now we promote for Shogi, if needed */
7308     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7309         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7310   }
7311
7312     if (gameInfo.holdingsWidth != 0) {
7313
7314       /* !!A lot more code needs to be written to support holdings  */
7315       /* [HGM] OK, so I have written it. Holdings are stored in the */
7316       /* penultimate board files, so they are automaticlly stored   */
7317       /* in the game history.                                       */
7318       if (fromY == DROP_RANK) {
7319         /* Delete from holdings, by decreasing count */
7320         /* and erasing image if necessary            */
7321         p = (int) fromX;
7322         if(p < (int) BlackPawn) { /* white drop */
7323              p -= (int)WhitePawn;
7324              if(p >= gameInfo.holdingsSize) p = 0;
7325              if(--board[p][BOARD_WIDTH-2] == 0)
7326                   board[p][BOARD_WIDTH-1] = EmptySquare;
7327         } else {                  /* black drop */
7328              p -= (int)BlackPawn;
7329              if(p >= gameInfo.holdingsSize) p = 0;
7330              if(--board[BOARD_HEIGHT-1-p][1] == 0)
7331                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7332         }
7333       }
7334       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7335           && gameInfo.variant != VariantBughouse        ) {
7336         /* [HGM] holdings: Add to holdings, if holdings exist */
7337         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7338                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7339                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7340         }
7341         p = (int) captured;
7342         if (p >= (int) BlackPawn) {
7343           p -= (int)BlackPawn;
7344           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7345                   /* in Shogi restore piece to its original  first */
7346                   captured = (ChessSquare) (DEMOTED captured);
7347                   p = DEMOTED p;
7348           }
7349           p = PieceToNumber((ChessSquare)p);
7350           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7351           board[p][BOARD_WIDTH-2]++;
7352           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7353         } else {
7354           p -= (int)WhitePawn;
7355           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7356                   captured = (ChessSquare) (DEMOTED captured);
7357                   p = DEMOTED p;
7358           }
7359           p = PieceToNumber((ChessSquare)p);
7360           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7361           board[BOARD_HEIGHT-1-p][1]++;
7362           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7363         }
7364       }
7365
7366     } else if (gameInfo.variant == VariantAtomic) {
7367       if (captured != EmptySquare) {
7368         int y, x;
7369         for (y = toY-1; y <= toY+1; y++) {
7370           for (x = toX-1; x <= toX+1; x++) {
7371             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7372                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7373               board[y][x] = EmptySquare;
7374             }
7375           }
7376         }
7377         board[toY][toX] = EmptySquare;
7378       }
7379     }
7380     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7381         /* [HGM] Shogi promotions */
7382         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7383     }
7384
7385     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7386                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7387         // [HGM] superchess: take promotion piece out of holdings
7388         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7389         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7390             if(!--board[k][BOARD_WIDTH-2])
7391                 board[k][BOARD_WIDTH-1] = EmptySquare;
7392         } else {
7393             if(!--board[BOARD_HEIGHT-1-k][1])
7394                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7395         }
7396     }
7397
7398 }
7399
7400 /* Updates forwardMostMove */
7401 void
7402 MakeMove(fromX, fromY, toX, toY, promoChar)
7403      int fromX, fromY, toX, toY;
7404      int promoChar;
7405 {
7406 //    forwardMostMove++; // [HGM] bare: moved downstream
7407
7408     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7409         int timeLeft; static int lastLoadFlag=0; int king, piece;
7410         piece = boards[forwardMostMove][fromY][fromX];
7411         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7412         if(gameInfo.variant == VariantKnightmate)
7413             king += (int) WhiteUnicorn - (int) WhiteKing;
7414         if(forwardMostMove == 0) {
7415             if(blackPlaysFirst) 
7416                 fprintf(serverMoves, "%s;", second.tidy);
7417             fprintf(serverMoves, "%s;", first.tidy);
7418             if(!blackPlaysFirst) 
7419                 fprintf(serverMoves, "%s;", second.tidy);
7420         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7421         lastLoadFlag = loadFlag;
7422         // print base move
7423         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7424         // print castling suffix
7425         if( toY == fromY && piece == king ) {
7426             if(toX-fromX > 1)
7427                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7428             if(fromX-toX >1)
7429                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7430         }
7431         // e.p. suffix
7432         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7433              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7434              boards[forwardMostMove][toY][toX] == EmptySquare
7435              && fromX != toX )
7436                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7437         // promotion suffix
7438         if(promoChar != NULLCHAR)
7439                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7440         if(!loadFlag) {
7441             fprintf(serverMoves, "/%d/%d",
7442                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7443             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7444             else                      timeLeft = blackTimeRemaining/1000;
7445             fprintf(serverMoves, "/%d", timeLeft);
7446         }
7447         fflush(serverMoves);
7448     }
7449
7450     if (forwardMostMove+1 >= MAX_MOVES) {
7451       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7452                         0, 1);
7453       return;
7454     }
7455     SwitchClocks();
7456     timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
7457     timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
7458     if (commentList[forwardMostMove+1] != NULL) {
7459         free(commentList[forwardMostMove+1]);
7460         commentList[forwardMostMove+1] = NULL;
7461     }
7462     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7463     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7464     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7465                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7466     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7467     gameInfo.result = GameUnfinished;
7468     if (gameInfo.resultDetails != NULL) {
7469         free(gameInfo.resultDetails);
7470         gameInfo.resultDetails = NULL;
7471     }
7472     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7473                               moveList[forwardMostMove - 1]);
7474     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7475                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7476                              fromY, fromX, toY, toX, promoChar,
7477                              parseList[forwardMostMove - 1]);
7478     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7479                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7480                             castlingRights[forwardMostMove]) ) {
7481       case MT_NONE:
7482       case MT_STALEMATE:
7483       default:
7484         break;
7485       case MT_CHECK:
7486         if(gameInfo.variant != VariantShogi)
7487             strcat(parseList[forwardMostMove - 1], "+");
7488         break;
7489       case MT_CHECKMATE:
7490       case MT_STAINMATE:
7491         strcat(parseList[forwardMostMove - 1], "#");
7492         break;
7493     }
7494     if (appData.debugMode) {
7495         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7496     }
7497
7498 }
7499
7500 /* Updates currentMove if not pausing */
7501 void
7502 ShowMove(fromX, fromY, toX, toY)
7503 {
7504     int instant = (gameMode == PlayFromGameFile) ?
7505         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7506     if(appData.noGUI) return;
7507     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7508         if (!instant) {
7509             if (forwardMostMove == currentMove + 1) {
7510                 AnimateMove(boards[forwardMostMove - 1],
7511                             fromX, fromY, toX, toY);
7512             }
7513             if (appData.highlightLastMove) {
7514                 SetHighlights(fromX, fromY, toX, toY);
7515             }
7516         }
7517         currentMove = forwardMostMove;
7518     }
7519
7520     if (instant) return;
7521
7522     DisplayMove(currentMove - 1);
7523     DrawPosition(FALSE, boards[currentMove]);
7524     DisplayBothClocks();
7525     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7526 }
7527
7528 void SendEgtPath(ChessProgramState *cps)
7529 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7530         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7531
7532         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7533
7534         while(*p) {
7535             char c, *q = name+1, *r, *s;
7536
7537             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7538             while(*p && *p != ',') *q++ = *p++;
7539             *q++ = ':'; *q = 0;
7540             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7541                 strcmp(name, ",nalimov:") == 0 ) {
7542                 // take nalimov path from the menu-changeable option first, if it is defined
7543                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7544                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7545             } else
7546             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7547                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7548                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7549                 s = r = StrStr(s, ":") + 1; // beginning of path info
7550                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7551                 c = *r; *r = 0;             // temporarily null-terminate path info
7552                     *--q = 0;               // strip of trailig ':' from name
7553                     sprintf(buf, "egtbpath %s %s\n", name+1, s);
7554                 *r = c;
7555                 SendToProgram(buf,cps);     // send egtbpath command for this format
7556             }
7557             if(*p == ',') p++; // read away comma to position for next format name
7558         }
7559 }
7560
7561 void
7562 InitChessProgram(cps, setup)
7563      ChessProgramState *cps;
7564      int setup; /* [HGM] needed to setup FRC opening position */
7565 {
7566     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7567     if (appData.noChessProgram) return;
7568     hintRequested = FALSE;
7569     bookRequested = FALSE;
7570
7571     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7572     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7573     if(cps->memSize) { /* [HGM] memory */
7574         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7575         SendToProgram(buf, cps);
7576     }
7577     SendEgtPath(cps); /* [HGM] EGT */
7578     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7579         sprintf(buf, "cores %d\n", appData.smpCores);
7580         SendToProgram(buf, cps);
7581     }
7582
7583     SendToProgram(cps->initString, cps);
7584     if (gameInfo.variant != VariantNormal &&
7585         gameInfo.variant != VariantLoadable
7586         /* [HGM] also send variant if board size non-standard */
7587         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7588                                             ) {
7589       char *v = VariantName(gameInfo.variant);
7590       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7591         /* [HGM] in protocol 1 we have to assume all variants valid */
7592         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7593         DisplayFatalError(buf, 0, 1);
7594         return;
7595       }
7596
7597       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7598       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7599       if( gameInfo.variant == VariantXiangqi )
7600            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7601       if( gameInfo.variant == VariantShogi )
7602            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7603       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7604            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7605       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7606                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7607            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7608       if( gameInfo.variant == VariantCourier )
7609            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7610       if( gameInfo.variant == VariantSuper )
7611            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7612       if( gameInfo.variant == VariantGreat )
7613            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7614
7615       if(overruled) {
7616            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7617                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7618            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7619            if(StrStr(cps->variants, b) == NULL) { 
7620                // specific sized variant not known, check if general sizing allowed
7621                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7622                    if(StrStr(cps->variants, "boardsize") == NULL) {
7623                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7624                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7625                        DisplayFatalError(buf, 0, 1);
7626                        return;
7627                    }
7628                    /* [HGM] here we really should compare with the maximum supported board size */
7629                }
7630            }
7631       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7632       sprintf(buf, "variant %s\n", b);
7633       SendToProgram(buf, cps);
7634     }
7635     currentlyInitializedVariant = gameInfo.variant;
7636
7637     /* [HGM] send opening position in FRC to first engine */
7638     if(setup) {
7639           SendToProgram("force\n", cps);
7640           SendBoard(cps, 0);
7641           /* engine is now in force mode! Set flag to wake it up after first move. */
7642           setboardSpoiledMachineBlack = 1;
7643     }
7644
7645     if (cps->sendICS) {
7646       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7647       SendToProgram(buf, cps);
7648     }
7649     cps->maybeThinking = FALSE;
7650     cps->offeredDraw = 0;
7651     if (!appData.icsActive) {
7652         SendTimeControl(cps, movesPerSession, timeControl,
7653                         timeIncrement, appData.searchDepth,
7654                         searchTime);
7655     }
7656     if (appData.showThinking 
7657         // [HGM] thinking: four options require thinking output to be sent
7658         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7659                                 ) {
7660         SendToProgram("post\n", cps);
7661     }
7662     SendToProgram("hard\n", cps);
7663     if (!appData.ponderNextMove) {
7664         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7665            it without being sure what state we are in first.  "hard"
7666            is not a toggle, so that one is OK.
7667          */
7668         SendToProgram("easy\n", cps);
7669     }
7670     if (cps->usePing) {
7671       sprintf(buf, "ping %d\n", ++cps->lastPing);
7672       SendToProgram(buf, cps);
7673     }
7674     cps->initDone = TRUE;
7675 }   
7676
7677
7678 void
7679 StartChessProgram(cps)
7680      ChessProgramState *cps;
7681 {
7682     char buf[MSG_SIZ];
7683     int err;
7684
7685     if (appData.noChessProgram) return;
7686     cps->initDone = FALSE;
7687
7688     if (strcmp(cps->host, "localhost") == 0) {
7689         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7690     } else if (*appData.remoteShell == NULLCHAR) {
7691         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7692     } else {
7693         if (*appData.remoteUser == NULLCHAR) {
7694           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7695                     cps->program);
7696         } else {
7697           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7698                     cps->host, appData.remoteUser, cps->program);
7699         }
7700         err = StartChildProcess(buf, "", &cps->pr);
7701     }
7702     
7703     if (err != 0) {
7704         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7705         DisplayFatalError(buf, err, 1);
7706         cps->pr = NoProc;
7707         cps->isr = NULL;
7708         return;
7709     }
7710     
7711     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7712     if (cps->protocolVersion > 1) {
7713       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7714       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7715       cps->comboCnt = 0;  //                and values of combo boxes
7716       SendToProgram(buf, cps);
7717     } else {
7718       SendToProgram("xboard\n", cps);
7719     }
7720 }
7721
7722
7723 void
7724 TwoMachinesEventIfReady P((void))
7725 {
7726   if (first.lastPing != first.lastPong) {
7727     DisplayMessage("", _("Waiting for first chess program"));
7728     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7729     return;
7730   }
7731   if (second.lastPing != second.lastPong) {
7732     DisplayMessage("", _("Waiting for second chess program"));
7733     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7734     return;
7735   }
7736   ThawUI();
7737   TwoMachinesEvent();
7738 }
7739
7740 void
7741 NextMatchGame P((void))
7742 {
7743     int index; /* [HGM] autoinc: step lod index during match */
7744     Reset(FALSE, TRUE);
7745     if (*appData.loadGameFile != NULLCHAR) {
7746         index = appData.loadGameIndex;
7747         if(index < 0) { // [HGM] autoinc
7748             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7749             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7750         } 
7751         LoadGameFromFile(appData.loadGameFile,
7752                          index,
7753                          appData.loadGameFile, FALSE);
7754     } else if (*appData.loadPositionFile != NULLCHAR) {
7755         index = appData.loadPositionIndex;
7756         if(index < 0) { // [HGM] autoinc
7757             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7758             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7759         } 
7760         LoadPositionFromFile(appData.loadPositionFile,
7761                              index,
7762                              appData.loadPositionFile);
7763     }
7764     TwoMachinesEventIfReady();
7765 }
7766
7767 void UserAdjudicationEvent( int result )
7768 {
7769     ChessMove gameResult = GameIsDrawn;
7770
7771     if( result > 0 ) {
7772         gameResult = WhiteWins;
7773     }
7774     else if( result < 0 ) {
7775         gameResult = BlackWins;
7776     }
7777
7778     if( gameMode == TwoMachinesPlay ) {
7779         GameEnds( gameResult, "User adjudication", GE_XBOARD );
7780     }
7781 }
7782
7783
7784 // [HGM] save: calculate checksum of game to make games easily identifiable
7785 int StringCheckSum(char *s)
7786 {
7787         int i = 0;
7788         if(s==NULL) return 0;
7789         while(*s) i = i*259 + *s++;
7790         return i;
7791 }
7792
7793 int GameCheckSum()
7794 {
7795         int i, sum=0;
7796         for(i=backwardMostMove; i<forwardMostMove; i++) {
7797                 sum += pvInfoList[i].depth;
7798                 sum += StringCheckSum(parseList[i]);
7799                 sum += StringCheckSum(commentList[i]);
7800                 sum *= 261;
7801         }
7802         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7803         return sum + StringCheckSum(commentList[i]);
7804 } // end of save patch
7805
7806 void
7807 GameEnds(result, resultDetails, whosays)
7808      ChessMove result;
7809      char *resultDetails;
7810      int whosays;
7811 {
7812     GameMode nextGameMode;
7813     int isIcsGame;
7814     char buf[MSG_SIZ];
7815
7816     if(endingGame) return; /* [HGM] crash: forbid recursion */
7817     endingGame = 1;
7818
7819     if (appData.debugMode) {
7820       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7821               result, resultDetails ? resultDetails : "(null)", whosays);
7822     }
7823
7824     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7825         /* If we are playing on ICS, the server decides when the
7826            game is over, but the engine can offer to draw, claim 
7827            a draw, or resign. 
7828          */
7829 #if ZIPPY
7830         if (appData.zippyPlay && first.initDone) {
7831             if (result == GameIsDrawn) {
7832                 /* In case draw still needs to be claimed */
7833                 SendToICS(ics_prefix);
7834                 SendToICS("draw\n");
7835             } else if (StrCaseStr(resultDetails, "resign")) {
7836                 SendToICS(ics_prefix);
7837                 SendToICS("resign\n");
7838             }
7839         }
7840 #endif
7841         endingGame = 0; /* [HGM] crash */
7842         return;
7843     }
7844
7845     /* If we're loading the game from a file, stop */
7846     if (whosays == GE_FILE) {
7847       (void) StopLoadGameTimer();
7848       gameFileFP = NULL;
7849     }
7850
7851     /* Cancel draw offers */
7852     first.offeredDraw = second.offeredDraw = 0;
7853
7854     /* If this is an ICS game, only ICS can really say it's done;
7855        if not, anyone can. */
7856     isIcsGame = (gameMode == IcsPlayingWhite || 
7857                  gameMode == IcsPlayingBlack || 
7858                  gameMode == IcsObserving    || 
7859                  gameMode == IcsExamining);
7860
7861     if (!isIcsGame || whosays == GE_ICS) {
7862         /* OK -- not an ICS game, or ICS said it was done */
7863         StopClocks();
7864         if (!isIcsGame && !appData.noChessProgram) 
7865           SetUserThinkingEnables();
7866     
7867         /* [HGM] if a machine claims the game end we verify this claim */
7868         if(gameMode == TwoMachinesPlay && appData.testClaims) {
7869             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7870                 char claimer;
7871                 ChessMove trueResult = (ChessMove) -1;
7872
7873                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
7874                                             first.twoMachinesColor[0] :
7875                                             second.twoMachinesColor[0] ;
7876
7877                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7878                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7879                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7880                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7881                 } else
7882                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7883                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7884                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7885                 } else
7886                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7887                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7888                 }
7889
7890                 // now verify win claims, but not in drop games, as we don't understand those yet
7891                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7892                                                  || gameInfo.variant == VariantGreat) &&
7893                     (result == WhiteWins && claimer == 'w' ||
7894                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
7895                       if (appData.debugMode) {
7896                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
7897                                 result, epStatus[forwardMostMove], forwardMostMove);
7898                       }
7899                       if(result != trueResult) {
7900                               sprintf(buf, "False win claim: '%s'", resultDetails);
7901                               result = claimer == 'w' ? BlackWins : WhiteWins;
7902                               resultDetails = buf;
7903                       }
7904                 } else
7905                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7906                     && (forwardMostMove <= backwardMostMove ||
7907                         epStatus[forwardMostMove-1] > EP_DRAWS ||
7908                         (claimer=='b')==(forwardMostMove&1))
7909                                                                                   ) {
7910                       /* [HGM] verify: draws that were not flagged are false claims */
7911                       sprintf(buf, "False draw claim: '%s'", resultDetails);
7912                       result = claimer == 'w' ? BlackWins : WhiteWins;
7913                       resultDetails = buf;
7914                 }
7915                 /* (Claiming a loss is accepted no questions asked!) */
7916             }
7917             /* [HGM] bare: don't allow bare King to win */
7918             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7919                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
7920                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7921                && result != GameIsDrawn)
7922             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7923                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7924                         int p = (int)boards[forwardMostMove][i][j] - color;
7925                         if(p >= 0 && p <= (int)WhiteKing) k++;
7926                 }
7927                 if (appData.debugMode) {
7928                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7929                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7930                 }
7931                 if(k <= 1) {
7932                         result = GameIsDrawn;
7933                         sprintf(buf, "%s but bare king", resultDetails);
7934                         resultDetails = buf;
7935                 }
7936             }
7937         }
7938
7939
7940         if(serverMoves != NULL && !loadFlag) { char c = '=';
7941             if(result==WhiteWins) c = '+';
7942             if(result==BlackWins) c = '-';
7943             if(resultDetails != NULL)
7944                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7945         }
7946         if (resultDetails != NULL) {
7947             gameInfo.result = result;
7948             gameInfo.resultDetails = StrSave(resultDetails);
7949
7950             /* display last move only if game was not loaded from file */
7951             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7952                 DisplayMove(currentMove - 1);
7953     
7954             if (forwardMostMove != 0) {
7955                 if (gameMode != PlayFromGameFile && gameMode != EditGame
7956                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7957                                                                 ) {
7958                     if (*appData.saveGameFile != NULLCHAR) {
7959                         SaveGameToFile(appData.saveGameFile, TRUE);
7960                     } else if (appData.autoSaveGames) {
7961                         AutoSaveGame();
7962                     }
7963                     if (*appData.savePositionFile != NULLCHAR) {
7964                         SavePositionToFile(appData.savePositionFile);
7965                     }
7966                 }
7967             }
7968
7969             /* Tell program how game ended in case it is learning */
7970             /* [HGM] Moved this to after saving the PGN, just in case */
7971             /* engine died and we got here through time loss. In that */
7972             /* case we will get a fatal error writing the pipe, which */
7973             /* would otherwise lose us the PGN.                       */
7974             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
7975             /* output during GameEnds should never be fatal anymore   */
7976             if (gameMode == MachinePlaysWhite ||
7977                 gameMode == MachinePlaysBlack ||
7978                 gameMode == TwoMachinesPlay ||
7979                 gameMode == IcsPlayingWhite ||
7980                 gameMode == IcsPlayingBlack ||
7981                 gameMode == BeginningOfGame) {
7982                 char buf[MSG_SIZ];
7983                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7984                         resultDetails);
7985                 if (first.pr != NoProc) {
7986                     SendToProgram(buf, &first);
7987                 }
7988                 if (second.pr != NoProc &&
7989                     gameMode == TwoMachinesPlay) {
7990                     SendToProgram(buf, &second);
7991                 }
7992             }
7993         }
7994
7995         if (appData.icsActive) {
7996             if (appData.quietPlay &&
7997                 (gameMode == IcsPlayingWhite ||
7998                  gameMode == IcsPlayingBlack)) {
7999                 SendToICS(ics_prefix);
8000                 SendToICS("set shout 1\n");
8001             }
8002             nextGameMode = IcsIdle;
8003             ics_user_moved = FALSE;
8004             /* clean up premove.  It's ugly when the game has ended and the
8005              * premove highlights are still on the board.
8006              */
8007             if (gotPremove) {
8008               gotPremove = FALSE;
8009               ClearPremoveHighlights();
8010               DrawPosition(FALSE, boards[currentMove]);
8011             }
8012             if (whosays == GE_ICS) {
8013                 switch (result) {
8014                 case WhiteWins:
8015                     if (gameMode == IcsPlayingWhite)
8016                         PlayIcsWinSound();
8017                     else if(gameMode == IcsPlayingBlack)
8018                         PlayIcsLossSound();
8019                     break;
8020                 case BlackWins:
8021                     if (gameMode == IcsPlayingBlack)
8022                         PlayIcsWinSound();
8023                     else if(gameMode == IcsPlayingWhite)
8024                         PlayIcsLossSound();
8025                     break;
8026                 case GameIsDrawn:
8027                     PlayIcsDrawSound();
8028                     break;
8029                 default:
8030                     PlayIcsUnfinishedSound();
8031                 }
8032             }
8033         } else if (gameMode == EditGame ||
8034                    gameMode == PlayFromGameFile || 
8035                    gameMode == AnalyzeMode || 
8036                    gameMode == AnalyzeFile) {
8037             nextGameMode = gameMode;
8038         } else {
8039             nextGameMode = EndOfGame;
8040         }
8041         pausing = FALSE;
8042         ModeHighlight();
8043     } else {
8044         nextGameMode = gameMode;
8045     }
8046
8047     if (appData.noChessProgram) {
8048         gameMode = nextGameMode;
8049         ModeHighlight();
8050         endingGame = 0; /* [HGM] crash */
8051         return;
8052     }
8053
8054     if (first.reuse) {
8055         /* Put first chess program into idle state */
8056         if (first.pr != NoProc &&
8057             (gameMode == MachinePlaysWhite ||
8058              gameMode == MachinePlaysBlack ||
8059              gameMode == TwoMachinesPlay ||
8060              gameMode == IcsPlayingWhite ||
8061              gameMode == IcsPlayingBlack ||
8062              gameMode == BeginningOfGame)) {
8063             SendToProgram("force\n", &first);
8064             if (first.usePing) {
8065               char buf[MSG_SIZ];
8066               sprintf(buf, "ping %d\n", ++first.lastPing);
8067               SendToProgram(buf, &first);
8068             }
8069         }
8070     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8071         /* Kill off first chess program */
8072         if (first.isr != NULL)
8073           RemoveInputSource(first.isr);
8074         first.isr = NULL;
8075     
8076         if (first.pr != NoProc) {
8077             ExitAnalyzeMode();
8078             DoSleep( appData.delayBeforeQuit );
8079             SendToProgram("quit\n", &first);
8080             DoSleep( appData.delayAfterQuit );
8081             DestroyChildProcess(first.pr, first.useSigterm);
8082         }
8083         first.pr = NoProc;
8084     }
8085     if (second.reuse) {
8086         /* Put second chess program into idle state */
8087         if (second.pr != NoProc &&
8088             gameMode == TwoMachinesPlay) {
8089             SendToProgram("force\n", &second);
8090             if (second.usePing) {
8091               char buf[MSG_SIZ];
8092               sprintf(buf, "ping %d\n", ++second.lastPing);
8093               SendToProgram(buf, &second);
8094             }
8095         }
8096     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8097         /* Kill off second chess program */
8098         if (second.isr != NULL)
8099           RemoveInputSource(second.isr);
8100         second.isr = NULL;
8101     
8102         if (second.pr != NoProc) {
8103             DoSleep( appData.delayBeforeQuit );
8104             SendToProgram("quit\n", &second);
8105             DoSleep( appData.delayAfterQuit );
8106             DestroyChildProcess(second.pr, second.useSigterm);
8107         }
8108         second.pr = NoProc;
8109     }
8110
8111     if (matchMode && gameMode == TwoMachinesPlay) {
8112         switch (result) {
8113         case WhiteWins:
8114           if (first.twoMachinesColor[0] == 'w') {
8115             first.matchWins++;
8116           } else {
8117             second.matchWins++;
8118           }
8119           break;
8120         case BlackWins:
8121           if (first.twoMachinesColor[0] == 'b') {
8122             first.matchWins++;
8123           } else {
8124             second.matchWins++;
8125           }
8126           break;
8127         default:
8128           break;
8129         }
8130         if (matchGame < appData.matchGames) {
8131             char *tmp;
8132             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8133                 tmp = first.twoMachinesColor;
8134                 first.twoMachinesColor = second.twoMachinesColor;
8135                 second.twoMachinesColor = tmp;
8136             }
8137             gameMode = nextGameMode;
8138             matchGame++;
8139             if(appData.matchPause>10000 || appData.matchPause<10)
8140                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8141             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8142             endingGame = 0; /* [HGM] crash */
8143             return;
8144         } else {
8145             char buf[MSG_SIZ];
8146             gameMode = nextGameMode;
8147             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8148                     first.tidy, second.tidy,
8149                     first.matchWins, second.matchWins,
8150                     appData.matchGames - (first.matchWins + second.matchWins));
8151             DisplayFatalError(buf, 0, 0);
8152         }
8153     }
8154     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8155         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8156       ExitAnalyzeMode();
8157     gameMode = nextGameMode;
8158     ModeHighlight();
8159     endingGame = 0;  /* [HGM] crash */
8160 }
8161
8162 /* Assumes program was just initialized (initString sent).
8163    Leaves program in force mode. */
8164 void
8165 FeedMovesToProgram(cps, upto) 
8166      ChessProgramState *cps;
8167      int upto;
8168 {
8169     int i;
8170     
8171     if (appData.debugMode)
8172       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8173               startedFromSetupPosition ? "position and " : "",
8174               backwardMostMove, upto, cps->which);
8175     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8176         // [HGM] variantswitch: make engine aware of new variant
8177         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8178                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8179         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8180         SendToProgram(buf, cps);
8181         currentlyInitializedVariant = gameInfo.variant;
8182     }
8183     SendToProgram("force\n", cps);
8184     if (startedFromSetupPosition) {
8185         SendBoard(cps, backwardMostMove);
8186     if (appData.debugMode) {
8187         fprintf(debugFP, "feedMoves\n");
8188     }
8189     }
8190     for (i = backwardMostMove; i < upto; i++) {
8191         SendMoveToProgram(i, cps);
8192     }
8193 }
8194
8195
8196 void
8197 ResurrectChessProgram()
8198 {
8199      /* The chess program may have exited.
8200         If so, restart it and feed it all the moves made so far. */
8201
8202     if (appData.noChessProgram || first.pr != NoProc) return;
8203     
8204     StartChessProgram(&first);
8205     InitChessProgram(&first, FALSE);
8206     FeedMovesToProgram(&first, currentMove);
8207
8208     if (!first.sendTime) {
8209         /* can't tell gnuchess what its clock should read,
8210            so we bow to its notion. */
8211         ResetClocks();
8212         timeRemaining[0][currentMove] = whiteTimeRemaining;
8213         timeRemaining[1][currentMove] = blackTimeRemaining;
8214     }
8215
8216     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8217                 appData.icsEngineAnalyze) && first.analysisSupport) {
8218       SendToProgram("analyze\n", &first);
8219       first.analyzing = TRUE;
8220     }
8221 }
8222
8223 /*
8224  * Button procedures
8225  */
8226 void
8227 Reset(redraw, init)
8228      int redraw, init;
8229 {
8230     int i;
8231
8232     if (appData.debugMode) {
8233         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8234                 redraw, init, gameMode);
8235     }
8236     pausing = pauseExamInvalid = FALSE;
8237     startedFromSetupPosition = blackPlaysFirst = FALSE;
8238     firstMove = TRUE;
8239     whiteFlag = blackFlag = FALSE;
8240     userOfferedDraw = FALSE;
8241     hintRequested = bookRequested = FALSE;
8242     first.maybeThinking = FALSE;
8243     second.maybeThinking = FALSE;
8244     first.bookSuspend = FALSE; // [HGM] book
8245     second.bookSuspend = FALSE;
8246     thinkOutput[0] = NULLCHAR;
8247     lastHint[0] = NULLCHAR;
8248     ClearGameInfo(&gameInfo);
8249     gameInfo.variant = StringToVariant(appData.variant);
8250     ics_user_moved = ics_clock_paused = FALSE;
8251     ics_getting_history = H_FALSE;
8252     ics_gamenum = -1;
8253     white_holding[0] = black_holding[0] = NULLCHAR;
8254     ClearProgramStats();
8255     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8256     
8257     ResetFrontEnd();
8258     ClearHighlights();
8259     flipView = appData.flipView;
8260     ClearPremoveHighlights();
8261     gotPremove = FALSE;
8262     alarmSounded = FALSE;
8263
8264     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8265     if(appData.serverMovesName != NULL) {
8266         /* [HGM] prepare to make moves file for broadcasting */
8267         clock_t t = clock();
8268         if(serverMoves != NULL) fclose(serverMoves);
8269         serverMoves = fopen(appData.serverMovesName, "r");
8270         if(serverMoves != NULL) {
8271             fclose(serverMoves);
8272             /* delay 15 sec before overwriting, so all clients can see end */
8273             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8274         }
8275         serverMoves = fopen(appData.serverMovesName, "w");
8276     }
8277
8278     ExitAnalyzeMode();
8279     gameMode = BeginningOfGame;
8280     ModeHighlight();
8281     if(appData.icsActive) gameInfo.variant = VariantNormal;
8282     InitPosition(redraw);
8283     for (i = 0; i < MAX_MOVES; i++) {
8284         if (commentList[i] != NULL) {
8285             free(commentList[i]);
8286             commentList[i] = NULL;
8287         }
8288     }
8289     ResetClocks();
8290     timeRemaining[0][0] = whiteTimeRemaining;
8291     timeRemaining[1][0] = blackTimeRemaining;
8292     if (first.pr == NULL) {
8293         StartChessProgram(&first);
8294     }
8295     if (init) {
8296             InitChessProgram(&first, startedFromSetupPosition);
8297     }
8298     DisplayTitle("");
8299     DisplayMessage("", "");
8300     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8301     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8302 }
8303
8304 void
8305 AutoPlayGameLoop()
8306 {
8307     for (;;) {
8308         if (!AutoPlayOneMove())
8309           return;
8310         if (matchMode || appData.timeDelay == 0)
8311           continue;
8312         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8313           return;
8314         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8315         break;
8316     }
8317 }
8318
8319
8320 int
8321 AutoPlayOneMove()
8322 {
8323     int fromX, fromY, toX, toY;
8324
8325     if (appData.debugMode) {
8326       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8327     }
8328
8329     if (gameMode != PlayFromGameFile)
8330       return FALSE;
8331
8332     if (currentMove >= forwardMostMove) {
8333       gameMode = EditGame;
8334       ModeHighlight();
8335
8336       /* [AS] Clear current move marker at the end of a game */
8337       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8338
8339       return FALSE;
8340     }
8341     
8342     toX = moveList[currentMove][2] - AAA;
8343     toY = moveList[currentMove][3] - ONE;
8344
8345     if (moveList[currentMove][1] == '@') {
8346         if (appData.highlightLastMove) {
8347             SetHighlights(-1, -1, toX, toY);
8348         }
8349     } else {
8350         fromX = moveList[currentMove][0] - AAA;
8351         fromY = moveList[currentMove][1] - ONE;
8352
8353         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8354
8355         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8356
8357         if (appData.highlightLastMove) {
8358             SetHighlights(fromX, fromY, toX, toY);
8359         }
8360     }
8361     DisplayMove(currentMove);
8362     SendMoveToProgram(currentMove++, &first);
8363     DisplayBothClocks();
8364     DrawPosition(FALSE, boards[currentMove]);
8365     // [HGM] PV info: always display, routine tests if empty
8366     DisplayComment(currentMove - 1, commentList[currentMove]);
8367     return TRUE;
8368 }
8369
8370
8371 int
8372 LoadGameOneMove(readAhead)
8373      ChessMove readAhead;
8374 {
8375     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8376     char promoChar = NULLCHAR;
8377     ChessMove moveType;
8378     char move[MSG_SIZ];
8379     char *p, *q;
8380     
8381     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8382         gameMode != AnalyzeMode && gameMode != Training) {
8383         gameFileFP = NULL;
8384         return FALSE;
8385     }
8386     
8387     yyboardindex = forwardMostMove;
8388     if (readAhead != (ChessMove)0) {
8389       moveType = readAhead;
8390     } else {
8391       if (gameFileFP == NULL)
8392           return FALSE;
8393       moveType = (ChessMove) yylex();
8394     }
8395     
8396     done = FALSE;
8397     switch (moveType) {
8398       case Comment:
8399         if (appData.debugMode) 
8400           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8401         p = yy_text;
8402         if (*p == '{' || *p == '[' || *p == '(') {
8403             p[strlen(p) - 1] = NULLCHAR;
8404             p++;
8405         }
8406
8407         /* append the comment but don't display it */
8408         while (*p == '\n') p++;
8409         AppendComment(currentMove, p);
8410         return TRUE;
8411
8412       case WhiteCapturesEnPassant:
8413       case BlackCapturesEnPassant:
8414       case WhitePromotionChancellor:
8415       case BlackPromotionChancellor:
8416       case WhitePromotionArchbishop:
8417       case BlackPromotionArchbishop:
8418       case WhitePromotionCentaur:
8419       case BlackPromotionCentaur:
8420       case WhitePromotionQueen:
8421       case BlackPromotionQueen:
8422       case WhitePromotionRook:
8423       case BlackPromotionRook:
8424       case WhitePromotionBishop:
8425       case BlackPromotionBishop:
8426       case WhitePromotionKnight:
8427       case BlackPromotionKnight:
8428       case WhitePromotionKing:
8429       case BlackPromotionKing:
8430       case NormalMove:
8431       case WhiteKingSideCastle:
8432       case WhiteQueenSideCastle:
8433       case BlackKingSideCastle:
8434       case BlackQueenSideCastle:
8435       case WhiteKingSideCastleWild:
8436       case WhiteQueenSideCastleWild:
8437       case BlackKingSideCastleWild:
8438       case BlackQueenSideCastleWild:
8439       /* PUSH Fabien */
8440       case WhiteHSideCastleFR:
8441       case WhiteASideCastleFR:
8442       case BlackHSideCastleFR:
8443       case BlackASideCastleFR:
8444       /* POP Fabien */
8445         if (appData.debugMode)
8446           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8447         fromX = currentMoveString[0] - AAA;
8448         fromY = currentMoveString[1] - ONE;
8449         toX = currentMoveString[2] - AAA;
8450         toY = currentMoveString[3] - ONE;
8451         promoChar = currentMoveString[4];
8452         break;
8453
8454       case WhiteDrop:
8455       case BlackDrop:
8456         if (appData.debugMode)
8457           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8458         fromX = moveType == WhiteDrop ?
8459           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8460         (int) CharToPiece(ToLower(currentMoveString[0]));
8461         fromY = DROP_RANK;
8462         toX = currentMoveString[2] - AAA;
8463         toY = currentMoveString[3] - ONE;
8464         break;
8465
8466       case WhiteWins:
8467       case BlackWins:
8468       case GameIsDrawn:
8469       case GameUnfinished:
8470         if (appData.debugMode)
8471           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8472         p = strchr(yy_text, '{');
8473         if (p == NULL) p = strchr(yy_text, '(');
8474         if (p == NULL) {
8475             p = yy_text;
8476             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8477         } else {
8478             q = strchr(p, *p == '{' ? '}' : ')');
8479             if (q != NULL) *q = NULLCHAR;
8480             p++;
8481         }
8482         GameEnds(moveType, p, GE_FILE);
8483         done = TRUE;
8484         if (cmailMsgLoaded) {
8485             ClearHighlights();
8486             flipView = WhiteOnMove(currentMove);
8487             if (moveType == GameUnfinished) flipView = !flipView;
8488             if (appData.debugMode)
8489               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8490         }
8491         break;
8492
8493       case (ChessMove) 0:       /* end of file */
8494         if (appData.debugMode)
8495           fprintf(debugFP, "Parser hit end of file\n");
8496         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8497                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8498           case MT_NONE:
8499           case MT_CHECK:
8500             break;
8501           case MT_CHECKMATE:
8502           case MT_STAINMATE:
8503             if (WhiteOnMove(currentMove)) {
8504                 GameEnds(BlackWins, "Black mates", GE_FILE);
8505             } else {
8506                 GameEnds(WhiteWins, "White mates", GE_FILE);
8507             }
8508             break;
8509           case MT_STALEMATE:
8510             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8511             break;
8512         }
8513         done = TRUE;
8514         break;
8515
8516       case MoveNumberOne:
8517         if (lastLoadGameStart == GNUChessGame) {
8518             /* GNUChessGames have numbers, but they aren't move numbers */
8519             if (appData.debugMode)
8520               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8521                       yy_text, (int) moveType);
8522             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8523         }
8524         /* else fall thru */
8525
8526       case XBoardGame:
8527       case GNUChessGame:
8528       case PGNTag:
8529         /* Reached start of next game in file */
8530         if (appData.debugMode)
8531           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8532         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8533                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8534           case MT_NONE:
8535           case MT_CHECK:
8536             break;
8537           case MT_CHECKMATE:
8538           case MT_STAINMATE:
8539             if (WhiteOnMove(currentMove)) {
8540                 GameEnds(BlackWins, "Black mates", GE_FILE);
8541             } else {
8542                 GameEnds(WhiteWins, "White mates", GE_FILE);
8543             }
8544             break;
8545           case MT_STALEMATE:
8546             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8547             break;
8548         }
8549         done = TRUE;
8550         break;
8551
8552       case PositionDiagram:     /* should not happen; ignore */
8553       case ElapsedTime:         /* ignore */
8554       case NAG:                 /* ignore */
8555         if (appData.debugMode)
8556           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8557                   yy_text, (int) moveType);
8558         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8559
8560       case IllegalMove:
8561         if (appData.testLegality) {
8562             if (appData.debugMode)
8563               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8564             sprintf(move, _("Illegal move: %d.%s%s"),
8565                     (forwardMostMove / 2) + 1,
8566                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8567             DisplayError(move, 0);
8568             done = TRUE;
8569         } else {
8570             if (appData.debugMode)
8571               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8572                       yy_text, currentMoveString);
8573             fromX = currentMoveString[0] - AAA;
8574             fromY = currentMoveString[1] - ONE;
8575             toX = currentMoveString[2] - AAA;
8576             toY = currentMoveString[3] - ONE;
8577             promoChar = currentMoveString[4];
8578         }
8579         break;
8580
8581       case AmbiguousMove:
8582         if (appData.debugMode)
8583           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8584         sprintf(move, _("Ambiguous move: %d.%s%s"),
8585                 (forwardMostMove / 2) + 1,
8586                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8587         DisplayError(move, 0);
8588         done = TRUE;
8589         break;
8590
8591       default:
8592       case ImpossibleMove:
8593         if (appData.debugMode)
8594           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8595         sprintf(move, _("Illegal move: %d.%s%s"),
8596                 (forwardMostMove / 2) + 1,
8597                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8598         DisplayError(move, 0);
8599         done = TRUE;
8600         break;
8601     }
8602
8603     if (done) {
8604         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8605             DrawPosition(FALSE, boards[currentMove]);
8606             DisplayBothClocks();
8607             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8608               DisplayComment(currentMove - 1, commentList[currentMove]);
8609         }
8610         (void) StopLoadGameTimer();
8611         gameFileFP = NULL;
8612         cmailOldMove = forwardMostMove;
8613         return FALSE;
8614     } else {
8615         /* currentMoveString is set as a side-effect of yylex */
8616         strcat(currentMoveString, "\n");
8617         strcpy(moveList[forwardMostMove], currentMoveString);
8618         
8619         thinkOutput[0] = NULLCHAR;
8620         MakeMove(fromX, fromY, toX, toY, promoChar);
8621         currentMove = forwardMostMove;
8622         return TRUE;
8623     }
8624 }
8625
8626 /* Load the nth game from the given file */
8627 int
8628 LoadGameFromFile(filename, n, title, useList)
8629      char *filename;
8630      int n;
8631      char *title;
8632      /*Boolean*/ int useList;
8633 {
8634     FILE *f;
8635     char buf[MSG_SIZ];
8636
8637     if (strcmp(filename, "-") == 0) {
8638         f = stdin;
8639         title = "stdin";
8640     } else {
8641         f = fopen(filename, "rb");
8642         if (f == NULL) {
8643           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8644             DisplayError(buf, errno);
8645             return FALSE;
8646         }
8647     }
8648     if (fseek(f, 0, 0) == -1) {
8649         /* f is not seekable; probably a pipe */
8650         useList = FALSE;
8651     }
8652     if (useList && n == 0) {
8653         int error = GameListBuild(f);
8654         if (error) {
8655             DisplayError(_("Cannot build game list"), error);
8656         } else if (!ListEmpty(&gameList) &&
8657                    ((ListGame *) gameList.tailPred)->number > 1) {
8658             GameListPopUp(f, title);
8659             return TRUE;
8660         }
8661         GameListDestroy();
8662         n = 1;
8663     }
8664     if (n == 0) n = 1;
8665     return LoadGame(f, n, title, FALSE);
8666 }
8667
8668
8669 void
8670 MakeRegisteredMove()
8671 {
8672     int fromX, fromY, toX, toY;
8673     char promoChar;
8674     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8675         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8676           case CMAIL_MOVE:
8677           case CMAIL_DRAW:
8678             if (appData.debugMode)
8679               fprintf(debugFP, "Restoring %s for game %d\n",
8680                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8681     
8682             thinkOutput[0] = NULLCHAR;
8683             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8684             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8685             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8686             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8687             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8688             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8689             MakeMove(fromX, fromY, toX, toY, promoChar);
8690             ShowMove(fromX, fromY, toX, toY);
8691               
8692             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8693                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8694               case MT_NONE:
8695               case MT_CHECK:
8696                 break;
8697                 
8698               case MT_CHECKMATE:
8699               case MT_STAINMATE:
8700                 if (WhiteOnMove(currentMove)) {
8701                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8702                 } else {
8703                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8704                 }
8705                 break;
8706                 
8707               case MT_STALEMATE:
8708                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8709                 break;
8710             }
8711
8712             break;
8713             
8714           case CMAIL_RESIGN:
8715             if (WhiteOnMove(currentMove)) {
8716                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8717             } else {
8718                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8719             }
8720             break;
8721             
8722           case CMAIL_ACCEPT:
8723             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8724             break;
8725               
8726           default:
8727             break;
8728         }
8729     }
8730
8731     return;
8732 }
8733
8734 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8735 int
8736 CmailLoadGame(f, gameNumber, title, useList)
8737      FILE *f;
8738      int gameNumber;
8739      char *title;
8740      int useList;
8741 {
8742     int retVal;
8743
8744     if (gameNumber > nCmailGames) {
8745         DisplayError(_("No more games in this message"), 0);
8746         return FALSE;
8747     }
8748     if (f == lastLoadGameFP) {
8749         int offset = gameNumber - lastLoadGameNumber;
8750         if (offset == 0) {
8751             cmailMsg[0] = NULLCHAR;
8752             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8753                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8754                 nCmailMovesRegistered--;
8755             }
8756             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8757             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8758                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8759             }
8760         } else {
8761             if (! RegisterMove()) return FALSE;
8762         }
8763     }
8764
8765     retVal = LoadGame(f, gameNumber, title, useList);
8766
8767     /* Make move registered during previous look at this game, if any */
8768     MakeRegisteredMove();
8769
8770     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8771         commentList[currentMove]
8772           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8773         DisplayComment(currentMove - 1, commentList[currentMove]);
8774     }
8775
8776     return retVal;
8777 }
8778
8779 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8780 int
8781 ReloadGame(offset)
8782      int offset;
8783 {
8784     int gameNumber = lastLoadGameNumber + offset;
8785     if (lastLoadGameFP == NULL) {
8786         DisplayError(_("No game has been loaded yet"), 0);
8787         return FALSE;
8788     }
8789     if (gameNumber <= 0) {
8790         DisplayError(_("Can't back up any further"), 0);
8791         return FALSE;
8792     }
8793     if (cmailMsgLoaded) {
8794         return CmailLoadGame(lastLoadGameFP, gameNumber,
8795                              lastLoadGameTitle, lastLoadGameUseList);
8796     } else {
8797         return LoadGame(lastLoadGameFP, gameNumber,
8798                         lastLoadGameTitle, lastLoadGameUseList);
8799     }
8800 }
8801
8802
8803
8804 /* Load the nth game from open file f */
8805 int
8806 LoadGame(f, gameNumber, title, useList)
8807      FILE *f;
8808      int gameNumber;
8809      char *title;
8810      int useList;
8811 {
8812     ChessMove cm;
8813     char buf[MSG_SIZ];
8814     int gn = gameNumber;
8815     ListGame *lg = NULL;
8816     int numPGNTags = 0;
8817     int err;
8818     GameMode oldGameMode;
8819     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8820
8821     if (appData.debugMode) 
8822         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8823
8824     if (gameMode == Training )
8825         SetTrainingModeOff();
8826
8827     oldGameMode = gameMode;
8828     if (gameMode != BeginningOfGame) {
8829       Reset(FALSE, TRUE);
8830     }
8831
8832     gameFileFP = f;
8833     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8834         fclose(lastLoadGameFP);
8835     }
8836
8837     if (useList) {
8838         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8839         
8840         if (lg) {
8841             fseek(f, lg->offset, 0);
8842             GameListHighlight(gameNumber);
8843             gn = 1;
8844         }
8845         else {
8846             DisplayError(_("Game number out of range"), 0);
8847             return FALSE;
8848         }
8849     } else {
8850         GameListDestroy();
8851         if (fseek(f, 0, 0) == -1) {
8852             if (f == lastLoadGameFP ?
8853                 gameNumber == lastLoadGameNumber + 1 :
8854                 gameNumber == 1) {
8855                 gn = 1;
8856             } else {
8857                 DisplayError(_("Can't seek on game file"), 0);
8858                 return FALSE;
8859             }
8860         }
8861     }
8862     lastLoadGameFP = f;
8863     lastLoadGameNumber = gameNumber;
8864     strcpy(lastLoadGameTitle, title);
8865     lastLoadGameUseList = useList;
8866
8867     yynewfile(f);
8868
8869     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8870       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8871                 lg->gameInfo.black);
8872             DisplayTitle(buf);
8873     } else if (*title != NULLCHAR) {
8874         if (gameNumber > 1) {
8875             sprintf(buf, "%s %d", title, gameNumber);
8876             DisplayTitle(buf);
8877         } else {
8878             DisplayTitle(title);
8879         }
8880     }
8881
8882     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8883         gameMode = PlayFromGameFile;
8884         ModeHighlight();
8885     }
8886
8887     currentMove = forwardMostMove = backwardMostMove = 0;
8888     CopyBoard(boards[0], initialPosition);
8889     StopClocks();
8890
8891     /*
8892      * Skip the first gn-1 games in the file.
8893      * Also skip over anything that precedes an identifiable 
8894      * start of game marker, to avoid being confused by 
8895      * garbage at the start of the file.  Currently 
8896      * recognized start of game markers are the move number "1",
8897      * the pattern "gnuchess .* game", the pattern
8898      * "^[#;%] [^ ]* game file", and a PGN tag block.  
8899      * A game that starts with one of the latter two patterns
8900      * will also have a move number 1, possibly
8901      * following a position diagram.
8902      * 5-4-02: Let's try being more lenient and allowing a game to
8903      * start with an unnumbered move.  Does that break anything?
8904      */
8905     cm = lastLoadGameStart = (ChessMove) 0;
8906     while (gn > 0) {
8907         yyboardindex = forwardMostMove;
8908         cm = (ChessMove) yylex();
8909         switch (cm) {
8910           case (ChessMove) 0:
8911             if (cmailMsgLoaded) {
8912                 nCmailGames = CMAIL_MAX_GAMES - gn;
8913             } else {
8914                 Reset(TRUE, TRUE);
8915                 DisplayError(_("Game not found in file"), 0);
8916             }
8917             return FALSE;
8918
8919           case GNUChessGame:
8920           case XBoardGame:
8921             gn--;
8922             lastLoadGameStart = cm;
8923             break;
8924             
8925           case MoveNumberOne:
8926             switch (lastLoadGameStart) {
8927               case GNUChessGame:
8928               case XBoardGame:
8929               case PGNTag:
8930                 break;
8931               case MoveNumberOne:
8932               case (ChessMove) 0:
8933                 gn--;           /* count this game */
8934                 lastLoadGameStart = cm;
8935                 break;
8936               default:
8937                 /* impossible */
8938                 break;
8939             }
8940             break;
8941
8942           case PGNTag:
8943             switch (lastLoadGameStart) {
8944               case GNUChessGame:
8945               case PGNTag:
8946               case MoveNumberOne:
8947               case (ChessMove) 0:
8948                 gn--;           /* count this game */
8949                 lastLoadGameStart = cm;
8950                 break;
8951               case XBoardGame:
8952                 lastLoadGameStart = cm; /* game counted already */
8953                 break;
8954               default:
8955                 /* impossible */
8956                 break;
8957             }
8958             if (gn > 0) {
8959                 do {
8960                     yyboardindex = forwardMostMove;
8961                     cm = (ChessMove) yylex();
8962                 } while (cm == PGNTag || cm == Comment);
8963             }
8964             break;
8965
8966           case WhiteWins:
8967           case BlackWins:
8968           case GameIsDrawn:
8969             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8970                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
8971                     != CMAIL_OLD_RESULT) {
8972                     nCmailResults ++ ;
8973                     cmailResult[  CMAIL_MAX_GAMES
8974                                 - gn - 1] = CMAIL_OLD_RESULT;
8975                 }
8976             }
8977             break;
8978
8979           case NormalMove:
8980             /* Only a NormalMove can be at the start of a game
8981              * without a position diagram. */
8982             if (lastLoadGameStart == (ChessMove) 0) {
8983               gn--;
8984               lastLoadGameStart = MoveNumberOne;
8985             }
8986             break;
8987
8988           default:
8989             break;
8990         }
8991     }
8992     
8993     if (appData.debugMode)
8994       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8995
8996     if (cm == XBoardGame) {
8997         /* Skip any header junk before position diagram and/or move 1 */
8998         for (;;) {
8999             yyboardindex = forwardMostMove;
9000             cm = (ChessMove) yylex();
9001
9002             if (cm == (ChessMove) 0 ||
9003                 cm == GNUChessGame || cm == XBoardGame) {
9004                 /* Empty game; pretend end-of-file and handle later */
9005                 cm = (ChessMove) 0;
9006                 break;
9007             }
9008
9009             if (cm == MoveNumberOne || cm == PositionDiagram ||
9010                 cm == PGNTag || cm == Comment)
9011               break;
9012         }
9013     } else if (cm == GNUChessGame) {
9014         if (gameInfo.event != NULL) {
9015             free(gameInfo.event);
9016         }
9017         gameInfo.event = StrSave(yy_text);
9018     }   
9019
9020     startedFromSetupPosition = FALSE;
9021     while (cm == PGNTag) {
9022         if (appData.debugMode) 
9023           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9024         err = ParsePGNTag(yy_text, &gameInfo);
9025         if (!err) numPGNTags++;
9026
9027         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9028         if(gameInfo.variant != oldVariant) {
9029             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9030             InitPosition(TRUE);
9031             oldVariant = gameInfo.variant;
9032             if (appData.debugMode) 
9033               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9034         }
9035
9036
9037         if (gameInfo.fen != NULL) {
9038           Board initial_position;
9039           startedFromSetupPosition = TRUE;
9040           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9041             Reset(TRUE, TRUE);
9042             DisplayError(_("Bad FEN position in file"), 0);
9043             return FALSE;
9044           }
9045           CopyBoard(boards[0], initial_position);
9046           if (blackPlaysFirst) {
9047             currentMove = forwardMostMove = backwardMostMove = 1;
9048             CopyBoard(boards[1], initial_position);
9049             strcpy(moveList[0], "");
9050             strcpy(parseList[0], "");
9051             timeRemaining[0][1] = whiteTimeRemaining;
9052             timeRemaining[1][1] = blackTimeRemaining;
9053             if (commentList[0] != NULL) {
9054               commentList[1] = commentList[0];
9055               commentList[0] = NULL;
9056             }
9057           } else {
9058             currentMove = forwardMostMove = backwardMostMove = 0;
9059           }
9060           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9061           {   int i;
9062               initialRulePlies = FENrulePlies;
9063               epStatus[forwardMostMove] = FENepStatus;
9064               for( i=0; i< nrCastlingRights; i++ )
9065                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9066           }
9067           yyboardindex = forwardMostMove;
9068           free(gameInfo.fen);
9069           gameInfo.fen = NULL;
9070         }
9071
9072         yyboardindex = forwardMostMove;
9073         cm = (ChessMove) yylex();
9074
9075         /* Handle comments interspersed among the tags */
9076         while (cm == Comment) {
9077             char *p;
9078             if (appData.debugMode) 
9079               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9080             p = yy_text;
9081             if (*p == '{' || *p == '[' || *p == '(') {
9082                 p[strlen(p) - 1] = NULLCHAR;
9083                 p++;
9084             }
9085             while (*p == '\n') p++;
9086             AppendComment(currentMove, p);
9087             yyboardindex = forwardMostMove;
9088             cm = (ChessMove) yylex();
9089         }
9090     }
9091
9092     /* don't rely on existence of Event tag since if game was
9093      * pasted from clipboard the Event tag may not exist
9094      */
9095     if (numPGNTags > 0){
9096         char *tags;
9097         if (gameInfo.variant == VariantNormal) {
9098           gameInfo.variant = StringToVariant(gameInfo.event);
9099         }
9100         if (!matchMode) {
9101           if( appData.autoDisplayTags ) {
9102             tags = PGNTags(&gameInfo);
9103             TagsPopUp(tags, CmailMsg());
9104             free(tags);
9105           }
9106         }
9107     } else {
9108         /* Make something up, but don't display it now */
9109         SetGameInfo();
9110         TagsPopDown();
9111     }
9112
9113     if (cm == PositionDiagram) {
9114         int i, j;
9115         char *p;
9116         Board initial_position;
9117
9118         if (appData.debugMode)
9119           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9120
9121         if (!startedFromSetupPosition) {
9122             p = yy_text;
9123             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9124               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9125                 switch (*p) {
9126                   case '[':
9127                   case '-':
9128                   case ' ':
9129                   case '\t':
9130                   case '\n':
9131                   case '\r':
9132                     break;
9133                   default:
9134                     initial_position[i][j++] = CharToPiece(*p);
9135                     break;
9136                 }
9137             while (*p == ' ' || *p == '\t' ||
9138                    *p == '\n' || *p == '\r') p++;
9139         
9140             if (strncmp(p, "black", strlen("black"))==0)
9141               blackPlaysFirst = TRUE;
9142             else
9143               blackPlaysFirst = FALSE;
9144             startedFromSetupPosition = TRUE;
9145         
9146             CopyBoard(boards[0], initial_position);
9147             if (blackPlaysFirst) {
9148                 currentMove = forwardMostMove = backwardMostMove = 1;
9149                 CopyBoard(boards[1], initial_position);
9150                 strcpy(moveList[0], "");
9151                 strcpy(parseList[0], "");
9152                 timeRemaining[0][1] = whiteTimeRemaining;
9153                 timeRemaining[1][1] = blackTimeRemaining;
9154                 if (commentList[0] != NULL) {
9155                     commentList[1] = commentList[0];
9156                     commentList[0] = NULL;
9157                 }
9158             } else {
9159                 currentMove = forwardMostMove = backwardMostMove = 0;
9160             }
9161         }
9162         yyboardindex = forwardMostMove;
9163         cm = (ChessMove) yylex();
9164     }
9165
9166     if (first.pr == NoProc) {
9167         StartChessProgram(&first);
9168     }
9169     InitChessProgram(&first, FALSE);
9170     SendToProgram("force\n", &first);
9171     if (startedFromSetupPosition) {
9172         SendBoard(&first, forwardMostMove);
9173     if (appData.debugMode) {
9174         fprintf(debugFP, "Load Game\n");
9175     }
9176         DisplayBothClocks();
9177     }      
9178
9179     /* [HGM] server: flag to write setup moves in broadcast file as one */
9180     loadFlag = appData.suppressLoadMoves;
9181
9182     while (cm == Comment) {
9183         char *p;
9184         if (appData.debugMode) 
9185           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9186         p = yy_text;
9187         if (*p == '{' || *p == '[' || *p == '(') {
9188             p[strlen(p) - 1] = NULLCHAR;
9189             p++;
9190         }
9191         while (*p == '\n') p++;
9192         AppendComment(currentMove, p);
9193         yyboardindex = forwardMostMove;
9194         cm = (ChessMove) yylex();
9195     }
9196
9197     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9198         cm == WhiteWins || cm == BlackWins ||
9199         cm == GameIsDrawn || cm == GameUnfinished) {
9200         DisplayMessage("", _("No moves in game"));
9201         if (cmailMsgLoaded) {
9202             if (appData.debugMode)
9203               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9204             ClearHighlights();
9205             flipView = FALSE;
9206         }
9207         DrawPosition(FALSE, boards[currentMove]);
9208         DisplayBothClocks();
9209         gameMode = EditGame;
9210         ModeHighlight();
9211         gameFileFP = NULL;
9212         cmailOldMove = 0;
9213         return TRUE;
9214     }
9215
9216     // [HGM] PV info: routine tests if comment empty
9217     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9218         DisplayComment(currentMove - 1, commentList[currentMove]);
9219     }
9220     if (!matchMode && appData.timeDelay != 0) 
9221       DrawPosition(FALSE, boards[currentMove]);
9222
9223     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9224       programStats.ok_to_send = 1;
9225     }
9226
9227     /* if the first token after the PGN tags is a move
9228      * and not move number 1, retrieve it from the parser 
9229      */
9230     if (cm != MoveNumberOne)
9231         LoadGameOneMove(cm);
9232
9233     /* load the remaining moves from the file */
9234     while (LoadGameOneMove((ChessMove)0)) {
9235       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9236       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9237     }
9238
9239     /* rewind to the start of the game */
9240     currentMove = backwardMostMove;
9241
9242     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9243
9244     if (oldGameMode == AnalyzeFile ||
9245         oldGameMode == AnalyzeMode) {
9246       AnalyzeFileEvent();
9247     }
9248
9249     if (matchMode || appData.timeDelay == 0) {
9250       ToEndEvent();
9251       gameMode = EditGame;
9252       ModeHighlight();
9253     } else if (appData.timeDelay > 0) {
9254       AutoPlayGameLoop();
9255     }
9256
9257     if (appData.debugMode) 
9258         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9259
9260     loadFlag = 0; /* [HGM] true game starts */
9261     return TRUE;
9262 }
9263
9264 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9265 int
9266 ReloadPosition(offset)
9267      int offset;
9268 {
9269     int positionNumber = lastLoadPositionNumber + offset;
9270     if (lastLoadPositionFP == NULL) {
9271         DisplayError(_("No position has been loaded yet"), 0);
9272         return FALSE;
9273     }
9274     if (positionNumber <= 0) {
9275         DisplayError(_("Can't back up any further"), 0);
9276         return FALSE;
9277     }
9278     return LoadPosition(lastLoadPositionFP, positionNumber,
9279                         lastLoadPositionTitle);
9280 }
9281
9282 /* Load the nth position from the given file */
9283 int
9284 LoadPositionFromFile(filename, n, title)
9285      char *filename;
9286      int n;
9287      char *title;
9288 {
9289     FILE *f;
9290     char buf[MSG_SIZ];
9291
9292     if (strcmp(filename, "-") == 0) {
9293         return LoadPosition(stdin, n, "stdin");
9294     } else {
9295         f = fopen(filename, "rb");
9296         if (f == NULL) {
9297             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9298             DisplayError(buf, errno);
9299             return FALSE;
9300         } else {
9301             return LoadPosition(f, n, title);
9302         }
9303     }
9304 }
9305
9306 /* Load the nth position from the given open file, and close it */
9307 int
9308 LoadPosition(f, positionNumber, title)
9309      FILE *f;
9310      int positionNumber;
9311      char *title;
9312 {
9313     char *p, line[MSG_SIZ];
9314     Board initial_position;
9315     int i, j, fenMode, pn;
9316     
9317     if (gameMode == Training )
9318         SetTrainingModeOff();
9319
9320     if (gameMode != BeginningOfGame) {
9321         Reset(FALSE, TRUE);
9322     }
9323     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9324         fclose(lastLoadPositionFP);
9325     }
9326     if (positionNumber == 0) positionNumber = 1;
9327     lastLoadPositionFP = f;
9328     lastLoadPositionNumber = positionNumber;
9329     strcpy(lastLoadPositionTitle, title);
9330     if (first.pr == NoProc) {
9331       StartChessProgram(&first);
9332       InitChessProgram(&first, FALSE);
9333     }    
9334     pn = positionNumber;
9335     if (positionNumber < 0) {
9336         /* Negative position number means to seek to that byte offset */
9337         if (fseek(f, -positionNumber, 0) == -1) {
9338             DisplayError(_("Can't seek on position file"), 0);
9339             return FALSE;
9340         };
9341         pn = 1;
9342     } else {
9343         if (fseek(f, 0, 0) == -1) {
9344             if (f == lastLoadPositionFP ?
9345                 positionNumber == lastLoadPositionNumber + 1 :
9346                 positionNumber == 1) {
9347                 pn = 1;
9348             } else {
9349                 DisplayError(_("Can't seek on position file"), 0);
9350                 return FALSE;
9351             }
9352         }
9353     }
9354     /* See if this file is FEN or old-style xboard */
9355     if (fgets(line, MSG_SIZ, f) == NULL) {
9356         DisplayError(_("Position not found in file"), 0);
9357         return FALSE;
9358     }
9359 #if 0
9360     switch (line[0]) {
9361       case '#':  case 'x':
9362       default:
9363         fenMode = FALSE;
9364         break;
9365       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':
9366       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':
9367       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':
9368       case '7':  case '8':  case '9':
9369       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':
9370       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':
9371       case 'C':  case 'W':             case 'c':  case 'w': 
9372         fenMode = TRUE;
9373         break;
9374     }
9375 #else
9376     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9377     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9378 #endif
9379
9380     if (pn >= 2) {
9381         if (fenMode || line[0] == '#') pn--;
9382         while (pn > 0) {
9383             /* skip positions before number pn */
9384             if (fgets(line, MSG_SIZ, f) == NULL) {
9385                 Reset(TRUE, TRUE);
9386                 DisplayError(_("Position not found in file"), 0);
9387                 return FALSE;
9388             }
9389             if (fenMode || line[0] == '#') pn--;
9390         }
9391     }
9392
9393     if (fenMode) {
9394         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9395             DisplayError(_("Bad FEN position in file"), 0);
9396             return FALSE;
9397         }
9398     } else {
9399         (void) fgets(line, MSG_SIZ, f);
9400         (void) fgets(line, MSG_SIZ, f);
9401     
9402         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9403             (void) fgets(line, MSG_SIZ, f);
9404             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9405                 if (*p == ' ')
9406                   continue;
9407                 initial_position[i][j++] = CharToPiece(*p);
9408             }
9409         }
9410     
9411         blackPlaysFirst = FALSE;
9412         if (!feof(f)) {
9413             (void) fgets(line, MSG_SIZ, f);
9414             if (strncmp(line, "black", strlen("black"))==0)
9415               blackPlaysFirst = TRUE;
9416         }
9417     }
9418     startedFromSetupPosition = TRUE;
9419     
9420     SendToProgram("force\n", &first);
9421     CopyBoard(boards[0], initial_position);
9422     if (blackPlaysFirst) {
9423         currentMove = forwardMostMove = backwardMostMove = 1;
9424         strcpy(moveList[0], "");
9425         strcpy(parseList[0], "");
9426         CopyBoard(boards[1], initial_position);
9427         DisplayMessage("", _("Black to play"));
9428     } else {
9429         currentMove = forwardMostMove = backwardMostMove = 0;
9430         DisplayMessage("", _("White to play"));
9431     }
9432           /* [HGM] copy FEN attributes as well */
9433           {   int i;
9434               initialRulePlies = FENrulePlies;
9435               epStatus[forwardMostMove] = FENepStatus;
9436               for( i=0; i< nrCastlingRights; i++ )
9437                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9438           }
9439     SendBoard(&first, forwardMostMove);
9440     if (appData.debugMode) {
9441 int i, j;
9442   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9443   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9444         fprintf(debugFP, "Load Position\n");
9445     }
9446
9447     if (positionNumber > 1) {
9448         sprintf(line, "%s %d", title, positionNumber);
9449         DisplayTitle(line);
9450     } else {
9451         DisplayTitle(title);
9452     }
9453     gameMode = EditGame;
9454     ModeHighlight();
9455     ResetClocks();
9456     timeRemaining[0][1] = whiteTimeRemaining;
9457     timeRemaining[1][1] = blackTimeRemaining;
9458     DrawPosition(FALSE, boards[currentMove]);
9459    
9460     return TRUE;
9461 }
9462
9463
9464 void
9465 CopyPlayerNameIntoFileName(dest, src)
9466      char **dest, *src;
9467 {
9468     while (*src != NULLCHAR && *src != ',') {
9469         if (*src == ' ') {
9470             *(*dest)++ = '_';
9471             src++;
9472         } else {
9473             *(*dest)++ = *src++;
9474         }
9475     }
9476 }
9477
9478 char *DefaultFileName(ext)
9479      char *ext;
9480 {
9481     static char def[MSG_SIZ];
9482     char *p;
9483
9484     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9485         p = def;
9486         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9487         *p++ = '-';
9488         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9489         *p++ = '.';
9490         strcpy(p, ext);
9491     } else {
9492         def[0] = NULLCHAR;
9493     }
9494     return def;
9495 }
9496
9497 /* Save the current game to the given file */
9498 int
9499 SaveGameToFile(filename, append)
9500      char *filename;
9501      int append;
9502 {
9503     FILE *f;
9504     char buf[MSG_SIZ];
9505
9506     if (strcmp(filename, "-") == 0) {
9507         return SaveGame(stdout, 0, NULL);
9508     } else {
9509         f = fopen(filename, append ? "a" : "w");
9510         if (f == NULL) {
9511             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9512             DisplayError(buf, errno);
9513             return FALSE;
9514         } else {
9515             return SaveGame(f, 0, NULL);
9516         }
9517     }
9518 }
9519
9520 char *
9521 SavePart(str)
9522      char *str;
9523 {
9524     static char buf[MSG_SIZ];
9525     char *p;
9526     
9527     p = strchr(str, ' ');
9528     if (p == NULL) return str;
9529     strncpy(buf, str, p - str);
9530     buf[p - str] = NULLCHAR;
9531     return buf;
9532 }
9533
9534 #define PGN_MAX_LINE 75
9535
9536 #define PGN_SIDE_WHITE  0
9537 #define PGN_SIDE_BLACK  1
9538
9539 /* [AS] */
9540 static int FindFirstMoveOutOfBook( int side )
9541 {
9542     int result = -1;
9543
9544     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9545         int index = backwardMostMove;
9546         int has_book_hit = 0;
9547
9548         if( (index % 2) != side ) {
9549             index++;
9550         }
9551
9552         while( index < forwardMostMove ) {
9553             /* Check to see if engine is in book */
9554             int depth = pvInfoList[index].depth;
9555             int score = pvInfoList[index].score;
9556             int in_book = 0;
9557
9558             if( depth <= 2 ) {
9559                 in_book = 1;
9560             }
9561             else if( score == 0 && depth == 63 ) {
9562                 in_book = 1; /* Zappa */
9563             }
9564             else if( score == 2 && depth == 99 ) {
9565                 in_book = 1; /* Abrok */
9566             }
9567
9568             has_book_hit += in_book;
9569
9570             if( ! in_book ) {
9571                 result = index;
9572
9573                 break;
9574             }
9575
9576             index += 2;
9577         }
9578     }
9579
9580     return result;
9581 }
9582
9583 /* [AS] */
9584 void GetOutOfBookInfo( char * buf )
9585 {
9586     int oob[2];
9587     int i;
9588     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9589
9590     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9591     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9592
9593     *buf = '\0';
9594
9595     if( oob[0] >= 0 || oob[1] >= 0 ) {
9596         for( i=0; i<2; i++ ) {
9597             int idx = oob[i];
9598
9599             if( idx >= 0 ) {
9600                 if( i > 0 && oob[0] >= 0 ) {
9601                     strcat( buf, "   " );
9602                 }
9603
9604                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9605                 sprintf( buf+strlen(buf), "%s%.2f", 
9606                     pvInfoList[idx].score >= 0 ? "+" : "",
9607                     pvInfoList[idx].score / 100.0 );
9608             }
9609         }
9610     }
9611 }
9612
9613 /* Save game in PGN style and close the file */
9614 int
9615 SaveGamePGN(f)
9616      FILE *f;
9617 {
9618     int i, offset, linelen, newblock;
9619     time_t tm;
9620 //    char *movetext;
9621     char numtext[32];
9622     int movelen, numlen, blank;
9623     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9624
9625     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9626     
9627     tm = time((time_t *) NULL);
9628     
9629     PrintPGNTags(f, &gameInfo);
9630     
9631     if (backwardMostMove > 0 || startedFromSetupPosition) {
9632         char *fen = PositionToFEN(backwardMostMove, NULL);
9633         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9634         fprintf(f, "\n{--------------\n");
9635         PrintPosition(f, backwardMostMove);
9636         fprintf(f, "--------------}\n");
9637         free(fen);
9638     }
9639     else {
9640         /* [AS] Out of book annotation */
9641         if( appData.saveOutOfBookInfo ) {
9642             char buf[64];
9643
9644             GetOutOfBookInfo( buf );
9645
9646             if( buf[0] != '\0' ) {
9647                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9648             }
9649         }
9650
9651         fprintf(f, "\n");
9652     }
9653
9654     i = backwardMostMove;
9655     linelen = 0;
9656     newblock = TRUE;
9657
9658     while (i < forwardMostMove) {
9659         /* Print comments preceding this move */
9660         if (commentList[i] != NULL) {
9661             if (linelen > 0) fprintf(f, "\n");
9662             fprintf(f, "{\n%s}\n", commentList[i]);
9663             linelen = 0;
9664             newblock = TRUE;
9665         }
9666
9667         /* Format move number */
9668         if ((i % 2) == 0) {
9669             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9670         } else {
9671             if (newblock) {
9672                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9673             } else {
9674                 numtext[0] = NULLCHAR;
9675             }
9676         }
9677         numlen = strlen(numtext);
9678         newblock = FALSE;
9679
9680         /* Print move number */
9681         blank = linelen > 0 && numlen > 0;
9682         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9683             fprintf(f, "\n");
9684             linelen = 0;
9685             blank = 0;
9686         }
9687         if (blank) {
9688             fprintf(f, " ");
9689             linelen++;
9690         }
9691         fprintf(f, numtext);
9692         linelen += numlen;
9693
9694         /* Get move */
9695         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9696         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9697 #if 0
9698         // SavePart already does this!
9699         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9700                 int p = movelen - 1;
9701                 if(move_buffer[p] == ' ') p--;
9702                 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
9703                     while(p && move_buffer[--p] != '(');
9704                     if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
9705                 }
9706         }
9707 #endif
9708         /* Print move */
9709         blank = linelen > 0 && movelen > 0;
9710         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9711             fprintf(f, "\n");
9712             linelen = 0;
9713             blank = 0;
9714         }
9715         if (blank) {
9716             fprintf(f, " ");
9717             linelen++;
9718         }
9719         fprintf(f, move_buffer);
9720         linelen += movelen;
9721
9722         /* [AS] Add PV info if present */
9723         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9724             /* [HGM] add time */
9725             char buf[MSG_SIZ]; int seconds = 0;
9726
9727 #if 1
9728             if(i >= backwardMostMove) {
9729                 if(WhiteOnMove(i))
9730                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9731                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9732                 else
9733                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9734                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9735             }
9736             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9737 #else
9738             seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
9739 #endif
9740
9741             if( seconds <= 0) buf[0] = 0; else
9742             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9743                 seconds = (seconds + 4)/10; // round to full seconds
9744                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9745                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9746             }
9747
9748             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9749                 pvInfoList[i].score >= 0 ? "+" : "",
9750                 pvInfoList[i].score / 100.0,
9751                 pvInfoList[i].depth,
9752                 buf );
9753
9754             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9755
9756             /* Print score/depth */
9757             blank = linelen > 0 && movelen > 0;
9758             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9759                 fprintf(f, "\n");
9760                 linelen = 0;
9761                 blank = 0;
9762             }
9763             if (blank) {
9764                 fprintf(f, " ");
9765                 linelen++;
9766             }
9767             fprintf(f, move_buffer);
9768             linelen += movelen;
9769         }
9770
9771         i++;
9772     }
9773     
9774     /* Start a new line */
9775     if (linelen > 0) fprintf(f, "\n");
9776
9777     /* Print comments after last move */
9778     if (commentList[i] != NULL) {
9779         fprintf(f, "{\n%s}\n", commentList[i]);
9780     }
9781
9782     /* Print result */
9783     if (gameInfo.resultDetails != NULL &&
9784         gameInfo.resultDetails[0] != NULLCHAR) {
9785         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9786                 PGNResult(gameInfo.result));
9787     } else {
9788         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9789     }
9790
9791     fclose(f);
9792     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9793     return TRUE;
9794 }
9795
9796 /* Save game in old style and close the file */
9797 int
9798 SaveGameOldStyle(f)
9799      FILE *f;
9800 {
9801     int i, offset;
9802     time_t tm;
9803     
9804     tm = time((time_t *) NULL);
9805     
9806     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9807     PrintOpponents(f);
9808     
9809     if (backwardMostMove > 0 || startedFromSetupPosition) {
9810         fprintf(f, "\n[--------------\n");
9811         PrintPosition(f, backwardMostMove);
9812         fprintf(f, "--------------]\n");
9813     } else {
9814         fprintf(f, "\n");
9815     }
9816
9817     i = backwardMostMove;
9818     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9819
9820     while (i < forwardMostMove) {
9821         if (commentList[i] != NULL) {
9822             fprintf(f, "[%s]\n", commentList[i]);
9823         }
9824
9825         if ((i % 2) == 1) {
9826             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
9827             i++;
9828         } else {
9829             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
9830             i++;
9831             if (commentList[i] != NULL) {
9832                 fprintf(f, "\n");
9833                 continue;
9834             }
9835             if (i >= forwardMostMove) {
9836                 fprintf(f, "\n");
9837                 break;
9838             }
9839             fprintf(f, "%s\n", parseList[i]);
9840             i++;
9841         }
9842     }
9843     
9844     if (commentList[i] != NULL) {
9845         fprintf(f, "[%s]\n", commentList[i]);
9846     }
9847
9848     /* This isn't really the old style, but it's close enough */
9849     if (gameInfo.resultDetails != NULL &&
9850         gameInfo.resultDetails[0] != NULLCHAR) {
9851         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9852                 gameInfo.resultDetails);
9853     } else {
9854         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9855     }
9856
9857     fclose(f);
9858     return TRUE;
9859 }
9860
9861 /* Save the current game to open file f and close the file */
9862 int
9863 SaveGame(f, dummy, dummy2)
9864      FILE *f;
9865      int dummy;
9866      char *dummy2;
9867 {
9868     if (gameMode == EditPosition) EditPositionDone();
9869     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9870     if (appData.oldSaveStyle)
9871       return SaveGameOldStyle(f);
9872     else
9873       return SaveGamePGN(f);
9874 }
9875
9876 /* Save the current position to the given file */
9877 int
9878 SavePositionToFile(filename)
9879      char *filename;
9880 {
9881     FILE *f;
9882     char buf[MSG_SIZ];
9883
9884     if (strcmp(filename, "-") == 0) {
9885         return SavePosition(stdout, 0, NULL);
9886     } else {
9887         f = fopen(filename, "a");
9888         if (f == NULL) {
9889             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9890             DisplayError(buf, errno);
9891             return FALSE;
9892         } else {
9893             SavePosition(f, 0, NULL);
9894             return TRUE;
9895         }
9896     }
9897 }
9898
9899 /* Save the current position to the given open file and close the file */
9900 int
9901 SavePosition(f, dummy, dummy2)
9902      FILE *f;
9903      int dummy;
9904      char *dummy2;
9905 {
9906     time_t tm;
9907     char *fen;
9908     
9909     if (appData.oldSaveStyle) {
9910         tm = time((time_t *) NULL);
9911     
9912         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9913         PrintOpponents(f);
9914         fprintf(f, "[--------------\n");
9915         PrintPosition(f, currentMove);
9916         fprintf(f, "--------------]\n");
9917     } else {
9918         fen = PositionToFEN(currentMove, NULL);
9919         fprintf(f, "%s\n", fen);
9920         free(fen);
9921     }
9922     fclose(f);
9923     return TRUE;
9924 }
9925
9926 void
9927 ReloadCmailMsgEvent(unregister)
9928      int unregister;
9929 {
9930 #if !WIN32
9931     static char *inFilename = NULL;
9932     static char *outFilename;
9933     int i;
9934     struct stat inbuf, outbuf;
9935     int status;
9936     
9937     /* Any registered moves are unregistered if unregister is set, */
9938     /* i.e. invoked by the signal handler */
9939     if (unregister) {
9940         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9941             cmailMoveRegistered[i] = FALSE;
9942             if (cmailCommentList[i] != NULL) {
9943                 free(cmailCommentList[i]);
9944                 cmailCommentList[i] = NULL;
9945             }
9946         }
9947         nCmailMovesRegistered = 0;
9948     }
9949
9950     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9951         cmailResult[i] = CMAIL_NOT_RESULT;
9952     }
9953     nCmailResults = 0;
9954
9955     if (inFilename == NULL) {
9956         /* Because the filenames are static they only get malloced once  */
9957         /* and they never get freed                                      */
9958         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9959         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9960
9961         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9962         sprintf(outFilename, "%s.out", appData.cmailGameName);
9963     }
9964     
9965     status = stat(outFilename, &outbuf);
9966     if (status < 0) {
9967         cmailMailedMove = FALSE;
9968     } else {
9969         status = stat(inFilename, &inbuf);
9970         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9971     }
9972     
9973     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9974        counts the games, notes how each one terminated, etc.
9975        
9976        It would be nice to remove this kludge and instead gather all
9977        the information while building the game list.  (And to keep it
9978        in the game list nodes instead of having a bunch of fixed-size
9979        parallel arrays.)  Note this will require getting each game's
9980        termination from the PGN tags, as the game list builder does
9981        not process the game moves.  --mann
9982        */
9983     cmailMsgLoaded = TRUE;
9984     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9985     
9986     /* Load first game in the file or popup game menu */
9987     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9988
9989 #endif /* !WIN32 */
9990     return;
9991 }
9992
9993 int
9994 RegisterMove()
9995 {
9996     FILE *f;
9997     char string[MSG_SIZ];
9998
9999     if (   cmailMailedMove
10000         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10001         return TRUE;            /* Allow free viewing  */
10002     }
10003
10004     /* Unregister move to ensure that we don't leave RegisterMove        */
10005     /* with the move registered when the conditions for registering no   */
10006     /* longer hold                                                       */
10007     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10008         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10009         nCmailMovesRegistered --;
10010
10011         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10012           {
10013               free(cmailCommentList[lastLoadGameNumber - 1]);
10014               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10015           }
10016     }
10017
10018     if (cmailOldMove == -1) {
10019         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10020         return FALSE;
10021     }
10022
10023     if (currentMove > cmailOldMove + 1) {
10024         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10025         return FALSE;
10026     }
10027
10028     if (currentMove < cmailOldMove) {
10029         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10030         return FALSE;
10031     }
10032
10033     if (forwardMostMove > currentMove) {
10034         /* Silently truncate extra moves */
10035         TruncateGame();
10036     }
10037
10038     if (   (currentMove == cmailOldMove + 1)
10039         || (   (currentMove == cmailOldMove)
10040             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10041                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10042         if (gameInfo.result != GameUnfinished) {
10043             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10044         }
10045
10046         if (commentList[currentMove] != NULL) {
10047             cmailCommentList[lastLoadGameNumber - 1]
10048               = StrSave(commentList[currentMove]);
10049         }
10050         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10051
10052         if (appData.debugMode)
10053           fprintf(debugFP, "Saving %s for game %d\n",
10054                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10055
10056         sprintf(string,
10057                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10058         
10059         f = fopen(string, "w");
10060         if (appData.oldSaveStyle) {
10061             SaveGameOldStyle(f); /* also closes the file */
10062             
10063             sprintf(string, "%s.pos.out", appData.cmailGameName);
10064             f = fopen(string, "w");
10065             SavePosition(f, 0, NULL); /* also closes the file */
10066         } else {
10067             fprintf(f, "{--------------\n");
10068             PrintPosition(f, currentMove);
10069             fprintf(f, "--------------}\n\n");
10070             
10071             SaveGame(f, 0, NULL); /* also closes the file*/
10072         }
10073         
10074         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10075         nCmailMovesRegistered ++;
10076     } else if (nCmailGames == 1) {
10077         DisplayError(_("You have not made a move yet"), 0);
10078         return FALSE;
10079     }
10080
10081     return TRUE;
10082 }
10083
10084 void
10085 MailMoveEvent()
10086 {
10087 #if !WIN32
10088     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10089     FILE *commandOutput;
10090     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10091     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10092     int nBuffers;
10093     int i;
10094     int archived;
10095     char *arcDir;
10096
10097     if (! cmailMsgLoaded) {
10098         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10099         return;
10100     }
10101
10102     if (nCmailGames == nCmailResults) {
10103         DisplayError(_("No unfinished games"), 0);
10104         return;
10105     }
10106
10107 #if CMAIL_PROHIBIT_REMAIL
10108     if (cmailMailedMove) {
10109         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);
10110         DisplayError(msg, 0);
10111         return;
10112     }
10113 #endif
10114
10115     if (! (cmailMailedMove || RegisterMove())) return;
10116     
10117     if (   cmailMailedMove
10118         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10119         sprintf(string, partCommandString,
10120                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10121         commandOutput = popen(string, "r");
10122
10123         if (commandOutput == NULL) {
10124             DisplayError(_("Failed to invoke cmail"), 0);
10125         } else {
10126             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10127                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10128             }
10129             if (nBuffers > 1) {
10130                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10131                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10132                 nBytes = MSG_SIZ - 1;
10133             } else {
10134                 (void) memcpy(msg, buffer, nBytes);
10135             }
10136             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10137
10138             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10139                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10140
10141                 archived = TRUE;
10142                 for (i = 0; i < nCmailGames; i ++) {
10143                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10144                         archived = FALSE;
10145                     }
10146                 }
10147                 if (   archived
10148                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10149                         != NULL)) {
10150                     sprintf(buffer, "%s/%s.%s.archive",
10151                             arcDir,
10152                             appData.cmailGameName,
10153                             gameInfo.date);
10154                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10155                     cmailMsgLoaded = FALSE;
10156                 }
10157             }
10158
10159             DisplayInformation(msg);
10160             pclose(commandOutput);
10161         }
10162     } else {
10163         if ((*cmailMsg) != '\0') {
10164             DisplayInformation(cmailMsg);
10165         }
10166     }
10167
10168     return;
10169 #endif /* !WIN32 */
10170 }
10171
10172 char *
10173 CmailMsg()
10174 {
10175 #if WIN32
10176     return NULL;
10177 #else
10178     int  prependComma = 0;
10179     char number[5];
10180     char string[MSG_SIZ];       /* Space for game-list */
10181     int  i;
10182     
10183     if (!cmailMsgLoaded) return "";
10184
10185     if (cmailMailedMove) {
10186         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10187     } else {
10188         /* Create a list of games left */
10189         sprintf(string, "[");
10190         for (i = 0; i < nCmailGames; i ++) {
10191             if (! (   cmailMoveRegistered[i]
10192                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10193                 if (prependComma) {
10194                     sprintf(number, ",%d", i + 1);
10195                 } else {
10196                     sprintf(number, "%d", i + 1);
10197                     prependComma = 1;
10198                 }
10199                 
10200                 strcat(string, number);
10201             }
10202         }
10203         strcat(string, "]");
10204
10205         if (nCmailMovesRegistered + nCmailResults == 0) {
10206             switch (nCmailGames) {
10207               case 1:
10208                 sprintf(cmailMsg,
10209                         _("Still need to make move for game\n"));
10210                 break;
10211                 
10212               case 2:
10213                 sprintf(cmailMsg,
10214                         _("Still need to make moves for both games\n"));
10215                 break;
10216                 
10217               default:
10218                 sprintf(cmailMsg,
10219                         _("Still need to make moves for all %d games\n"),
10220                         nCmailGames);
10221                 break;
10222             }
10223         } else {
10224             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10225               case 1:
10226                 sprintf(cmailMsg,
10227                         _("Still need to make a move for game %s\n"),
10228                         string);
10229                 break;
10230                 
10231               case 0:
10232                 if (nCmailResults == nCmailGames) {
10233                     sprintf(cmailMsg, _("No unfinished games\n"));
10234                 } else {
10235                     sprintf(cmailMsg, _("Ready to send mail\n"));
10236                 }
10237                 break;
10238                 
10239               default:
10240                 sprintf(cmailMsg,
10241                         _("Still need to make moves for games %s\n"),
10242                         string);
10243             }
10244         }
10245     }
10246     return cmailMsg;
10247 #endif /* WIN32 */
10248 }
10249
10250 void
10251 ResetGameEvent()
10252 {
10253     if (gameMode == Training)
10254       SetTrainingModeOff();
10255
10256     Reset(TRUE, TRUE);
10257     cmailMsgLoaded = FALSE;
10258     if (appData.icsActive) {
10259       SendToICS(ics_prefix);
10260       SendToICS("refresh\n");
10261     }
10262 }
10263
10264 void
10265 ExitEvent(status)
10266      int status;
10267 {
10268     exiting++;
10269     if (exiting > 2) {
10270       /* Give up on clean exit */
10271       exit(status);
10272     }
10273     if (exiting > 1) {
10274       /* Keep trying for clean exit */
10275       return;
10276     }
10277
10278     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10279
10280     if (telnetISR != NULL) {
10281       RemoveInputSource(telnetISR);
10282     }
10283     if (icsPR != NoProc) {
10284       DestroyChildProcess(icsPR, TRUE);
10285     }
10286 #if 0
10287     /* Save game if resource set and not already saved by GameEnds() */
10288     if ((gameInfo.resultDetails == NULL || errorExitFlag )
10289                              && forwardMostMove > 0) {
10290       if (*appData.saveGameFile != NULLCHAR) {
10291         SaveGameToFile(appData.saveGameFile, TRUE);
10292       } else if (appData.autoSaveGames) {
10293         AutoSaveGame();
10294       }
10295       if (*appData.savePositionFile != NULLCHAR) {
10296         SavePositionToFile(appData.savePositionFile);
10297       }
10298     }
10299     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10300 #else
10301     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10302     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10303 #endif
10304     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10305     /* make sure this other one finishes before killing it!                  */
10306     if(endingGame) { int count = 0;
10307         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10308         while(endingGame && count++ < 10) DoSleep(1);
10309         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10310     }
10311
10312     /* Kill off chess programs */
10313     if (first.pr != NoProc) {
10314         ExitAnalyzeMode();
10315         
10316         DoSleep( appData.delayBeforeQuit );
10317         SendToProgram("quit\n", &first);
10318         DoSleep( appData.delayAfterQuit );
10319         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10320     }
10321     if (second.pr != NoProc) {
10322         DoSleep( appData.delayBeforeQuit );
10323         SendToProgram("quit\n", &second);
10324         DoSleep( appData.delayAfterQuit );
10325         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10326     }
10327     if (first.isr != NULL) {
10328         RemoveInputSource(first.isr);
10329     }
10330     if (second.isr != NULL) {
10331         RemoveInputSource(second.isr);
10332     }
10333
10334     ShutDownFrontEnd();
10335     exit(status);
10336 }
10337
10338 void
10339 PauseEvent()
10340 {
10341     if (appData.debugMode)
10342         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10343     if (pausing) {
10344         pausing = FALSE;
10345         ModeHighlight();
10346         if (gameMode == MachinePlaysWhite ||
10347             gameMode == MachinePlaysBlack) {
10348             StartClocks();
10349         } else {
10350             DisplayBothClocks();
10351         }
10352         if (gameMode == PlayFromGameFile) {
10353             if (appData.timeDelay >= 0) 
10354                 AutoPlayGameLoop();
10355         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10356             Reset(FALSE, TRUE);
10357             SendToICS(ics_prefix);
10358             SendToICS("refresh\n");
10359         } else if (currentMove < forwardMostMove) {
10360             ForwardInner(forwardMostMove);
10361         }
10362         pauseExamInvalid = FALSE;
10363     } else {
10364         switch (gameMode) {
10365           default:
10366             return;
10367           case IcsExamining:
10368             pauseExamForwardMostMove = forwardMostMove;
10369             pauseExamInvalid = FALSE;
10370             /* fall through */
10371           case IcsObserving:
10372           case IcsPlayingWhite:
10373           case IcsPlayingBlack:
10374             pausing = TRUE;
10375             ModeHighlight();
10376             return;
10377           case PlayFromGameFile:
10378             (void) StopLoadGameTimer();
10379             pausing = TRUE;
10380             ModeHighlight();
10381             break;
10382           case BeginningOfGame:
10383             if (appData.icsActive) return;
10384             /* else fall through */
10385           case MachinePlaysWhite:
10386           case MachinePlaysBlack:
10387           case TwoMachinesPlay:
10388             if (forwardMostMove == 0)
10389               return;           /* don't pause if no one has moved */
10390             if ((gameMode == MachinePlaysWhite &&
10391                  !WhiteOnMove(forwardMostMove)) ||
10392                 (gameMode == MachinePlaysBlack &&
10393                  WhiteOnMove(forwardMostMove))) {
10394                 StopClocks();
10395             }
10396             pausing = TRUE;
10397             ModeHighlight();
10398             break;
10399         }
10400     }
10401 }
10402
10403 void
10404 EditCommentEvent()
10405 {
10406     char title[MSG_SIZ];
10407
10408     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10409         strcpy(title, _("Edit comment"));
10410     } else {
10411         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10412                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10413                 parseList[currentMove - 1]);
10414     }
10415
10416     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10417 }
10418
10419
10420 void
10421 EditTagsEvent()
10422 {
10423     char *tags = PGNTags(&gameInfo);
10424     EditTagsPopUp(tags);
10425     free(tags);
10426 }
10427
10428 void
10429 AnalyzeModeEvent()
10430 {
10431     if (appData.noChessProgram || gameMode == AnalyzeMode)
10432       return;
10433
10434     if (gameMode != AnalyzeFile) {
10435         if (!appData.icsEngineAnalyze) {
10436                EditGameEvent();
10437                if (gameMode != EditGame) return;
10438         }
10439         ResurrectChessProgram();
10440         SendToProgram("analyze\n", &first);
10441         first.analyzing = TRUE;
10442         /*first.maybeThinking = TRUE;*/
10443         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10444         AnalysisPopUp(_("Analysis"),
10445                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10446     }
10447     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10448     pausing = FALSE;
10449     ModeHighlight();
10450     SetGameInfo();
10451
10452     StartAnalysisClock();
10453     GetTimeMark(&lastNodeCountTime);
10454     lastNodeCount = 0;
10455 }
10456
10457 void
10458 AnalyzeFileEvent()
10459 {
10460     if (appData.noChessProgram || gameMode == AnalyzeFile)
10461       return;
10462
10463     if (gameMode != AnalyzeMode) {
10464         EditGameEvent();
10465         if (gameMode != EditGame) return;
10466         ResurrectChessProgram();
10467         SendToProgram("analyze\n", &first);
10468         first.analyzing = TRUE;
10469         /*first.maybeThinking = TRUE;*/
10470         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10471         AnalysisPopUp(_("Analysis"),
10472                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10473     }
10474     gameMode = AnalyzeFile;
10475     pausing = FALSE;
10476     ModeHighlight();
10477     SetGameInfo();
10478
10479     StartAnalysisClock();
10480     GetTimeMark(&lastNodeCountTime);
10481     lastNodeCount = 0;
10482 }
10483
10484 void
10485 MachineWhiteEvent()
10486 {
10487     char buf[MSG_SIZ];
10488     char *bookHit = NULL;
10489
10490     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10491       return;
10492
10493
10494     if (gameMode == PlayFromGameFile || 
10495         gameMode == TwoMachinesPlay  || 
10496         gameMode == Training         || 
10497         gameMode == AnalyzeMode      || 
10498         gameMode == EndOfGame)
10499         EditGameEvent();
10500
10501     if (gameMode == EditPosition) 
10502         EditPositionDone();
10503
10504     if (!WhiteOnMove(currentMove)) {
10505         DisplayError(_("It is not White's turn"), 0);
10506         return;
10507     }
10508   
10509     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10510       ExitAnalyzeMode();
10511
10512     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10513         gameMode == AnalyzeFile)
10514         TruncateGame();
10515
10516     ResurrectChessProgram();    /* in case it isn't running */
10517     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10518         gameMode = MachinePlaysWhite;
10519         ResetClocks();
10520     } else
10521     gameMode = MachinePlaysWhite;
10522     pausing = FALSE;
10523     ModeHighlight();
10524     SetGameInfo();
10525     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10526     DisplayTitle(buf);
10527     if (first.sendName) {
10528       sprintf(buf, "name %s\n", gameInfo.black);
10529       SendToProgram(buf, &first);
10530     }
10531     if (first.sendTime) {
10532       if (first.useColors) {
10533         SendToProgram("black\n", &first); /*gnu kludge*/
10534       }
10535       SendTimeRemaining(&first, TRUE);
10536     }
10537     if (first.useColors) {
10538       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10539     }
10540     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10541     SetMachineThinkingEnables();
10542     first.maybeThinking = TRUE;
10543     StartClocks();
10544
10545     if (appData.autoFlipView && !flipView) {
10546       flipView = !flipView;
10547       DrawPosition(FALSE, NULL);
10548       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10549     }
10550
10551     if(bookHit) { // [HGM] book: simulate book reply
10552         static char bookMove[MSG_SIZ]; // a bit generous?
10553
10554         programStats.nodes = programStats.depth = programStats.time = 
10555         programStats.score = programStats.got_only_move = 0;
10556         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10557
10558         strcpy(bookMove, "move ");
10559         strcat(bookMove, bookHit);
10560         HandleMachineMove(bookMove, &first);
10561     }
10562 }
10563
10564 void
10565 MachineBlackEvent()
10566 {
10567     char buf[MSG_SIZ];
10568    char *bookHit = NULL;
10569
10570     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10571         return;
10572
10573
10574     if (gameMode == PlayFromGameFile || 
10575         gameMode == TwoMachinesPlay  || 
10576         gameMode == Training         || 
10577         gameMode == AnalyzeMode      || 
10578         gameMode == EndOfGame)
10579         EditGameEvent();
10580
10581     if (gameMode == EditPosition) 
10582         EditPositionDone();
10583
10584     if (WhiteOnMove(currentMove)) {
10585         DisplayError(_("It is not Black's turn"), 0);
10586         return;
10587     }
10588     
10589     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10590       ExitAnalyzeMode();
10591
10592     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10593         gameMode == AnalyzeFile)
10594         TruncateGame();
10595
10596     ResurrectChessProgram();    /* in case it isn't running */
10597     gameMode = MachinePlaysBlack;
10598     pausing = FALSE;
10599     ModeHighlight();
10600     SetGameInfo();
10601     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10602     DisplayTitle(buf);
10603     if (first.sendName) {
10604       sprintf(buf, "name %s\n", gameInfo.white);
10605       SendToProgram(buf, &first);
10606     }
10607     if (first.sendTime) {
10608       if (first.useColors) {
10609         SendToProgram("white\n", &first); /*gnu kludge*/
10610       }
10611       SendTimeRemaining(&first, FALSE);
10612     }
10613     if (first.useColors) {
10614       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10615     }
10616     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10617     SetMachineThinkingEnables();
10618     first.maybeThinking = TRUE;
10619     StartClocks();
10620
10621     if (appData.autoFlipView && flipView) {
10622       flipView = !flipView;
10623       DrawPosition(FALSE, NULL);
10624       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10625     }
10626     if(bookHit) { // [HGM] book: simulate book reply
10627         static char bookMove[MSG_SIZ]; // a bit generous?
10628
10629         programStats.nodes = programStats.depth = programStats.time = 
10630         programStats.score = programStats.got_only_move = 0;
10631         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10632
10633         strcpy(bookMove, "move ");
10634         strcat(bookMove, bookHit);
10635         HandleMachineMove(bookMove, &first);
10636     }
10637 }
10638
10639
10640 void
10641 DisplayTwoMachinesTitle()
10642 {
10643     char buf[MSG_SIZ];
10644     if (appData.matchGames > 0) {
10645         if (first.twoMachinesColor[0] == 'w') {
10646             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10647                     gameInfo.white, gameInfo.black,
10648                     first.matchWins, second.matchWins,
10649                     matchGame - 1 - (first.matchWins + second.matchWins));
10650         } else {
10651             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10652                     gameInfo.white, gameInfo.black,
10653                     second.matchWins, first.matchWins,
10654                     matchGame - 1 - (first.matchWins + second.matchWins));
10655         }
10656     } else {
10657         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10658     }
10659     DisplayTitle(buf);
10660 }
10661
10662 void
10663 TwoMachinesEvent P((void))
10664 {
10665     int i;
10666     char buf[MSG_SIZ];
10667     ChessProgramState *onmove;
10668     char *bookHit = NULL;
10669     
10670     if (appData.noChessProgram) return;
10671
10672     switch (gameMode) {
10673       case TwoMachinesPlay:
10674         return;
10675       case MachinePlaysWhite:
10676       case MachinePlaysBlack:
10677         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10678             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10679             return;
10680         }
10681         /* fall through */
10682       case BeginningOfGame:
10683       case PlayFromGameFile:
10684       case EndOfGame:
10685         EditGameEvent();
10686         if (gameMode != EditGame) return;
10687         break;
10688       case EditPosition:
10689         EditPositionDone();
10690         break;
10691       case AnalyzeMode:
10692       case AnalyzeFile:
10693         ExitAnalyzeMode();
10694         break;
10695       case EditGame:
10696       default:
10697         break;
10698     }
10699
10700     forwardMostMove = currentMove;
10701     ResurrectChessProgram();    /* in case first program isn't running */
10702
10703     if (second.pr == NULL) {
10704         StartChessProgram(&second);
10705         if (second.protocolVersion == 1) {
10706           TwoMachinesEventIfReady();
10707         } else {
10708           /* kludge: allow timeout for initial "feature" command */
10709           FreezeUI();
10710           DisplayMessage("", _("Starting second chess program"));
10711           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10712         }
10713         return;
10714     }
10715     DisplayMessage("", "");
10716     InitChessProgram(&second, FALSE);
10717     SendToProgram("force\n", &second);
10718     if (startedFromSetupPosition) {
10719         SendBoard(&second, backwardMostMove);
10720     if (appData.debugMode) {
10721         fprintf(debugFP, "Two Machines\n");
10722     }
10723     }
10724     for (i = backwardMostMove; i < forwardMostMove; i++) {
10725         SendMoveToProgram(i, &second);
10726     }
10727
10728     gameMode = TwoMachinesPlay;
10729     pausing = FALSE;
10730     ModeHighlight();
10731     SetGameInfo();
10732     DisplayTwoMachinesTitle();
10733     firstMove = TRUE;
10734     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10735         onmove = &first;
10736     } else {
10737         onmove = &second;
10738     }
10739
10740     SendToProgram(first.computerString, &first);
10741     if (first.sendName) {
10742       sprintf(buf, "name %s\n", second.tidy);
10743       SendToProgram(buf, &first);
10744     }
10745     SendToProgram(second.computerString, &second);
10746     if (second.sendName) {
10747       sprintf(buf, "name %s\n", first.tidy);
10748       SendToProgram(buf, &second);
10749     }
10750
10751     ResetClocks();
10752     if (!first.sendTime || !second.sendTime) {
10753         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10754         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10755     }
10756     if (onmove->sendTime) {
10757       if (onmove->useColors) {
10758         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10759       }
10760       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10761     }
10762     if (onmove->useColors) {
10763       SendToProgram(onmove->twoMachinesColor, onmove);
10764     }
10765     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10766 //    SendToProgram("go\n", onmove);
10767     onmove->maybeThinking = TRUE;
10768     SetMachineThinkingEnables();
10769
10770     StartClocks();
10771
10772     if(bookHit) { // [HGM] book: simulate book reply
10773         static char bookMove[MSG_SIZ]; // a bit generous?
10774
10775         programStats.nodes = programStats.depth = programStats.time = 
10776         programStats.score = programStats.got_only_move = 0;
10777         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10778
10779         strcpy(bookMove, "move ");
10780         strcat(bookMove, bookHit);
10781         HandleMachineMove(bookMove, &first);
10782     }
10783 }
10784
10785 void
10786 TrainingEvent()
10787 {
10788     if (gameMode == Training) {
10789       SetTrainingModeOff();
10790       gameMode = PlayFromGameFile;
10791       DisplayMessage("", _("Training mode off"));
10792     } else {
10793       gameMode = Training;
10794       animateTraining = appData.animate;
10795
10796       /* make sure we are not already at the end of the game */
10797       if (currentMove < forwardMostMove) {
10798         SetTrainingModeOn();
10799         DisplayMessage("", _("Training mode on"));
10800       } else {
10801         gameMode = PlayFromGameFile;
10802         DisplayError(_("Already at end of game"), 0);
10803       }
10804     }
10805     ModeHighlight();
10806 }
10807
10808 void
10809 IcsClientEvent()
10810 {
10811     if (!appData.icsActive) return;
10812     switch (gameMode) {
10813       case IcsPlayingWhite:
10814       case IcsPlayingBlack:
10815       case IcsObserving:
10816       case IcsIdle:
10817       case BeginningOfGame:
10818       case IcsExamining:
10819         return;
10820
10821       case EditGame:
10822         break;
10823
10824       case EditPosition:
10825         EditPositionDone();
10826         break;
10827
10828       case AnalyzeMode:
10829       case AnalyzeFile:
10830         ExitAnalyzeMode();
10831         break;
10832         
10833       default:
10834         EditGameEvent();
10835         break;
10836     }
10837
10838     gameMode = IcsIdle;
10839     ModeHighlight();
10840     return;
10841 }
10842
10843
10844 void
10845 EditGameEvent()
10846 {
10847     int i;
10848
10849     switch (gameMode) {
10850       case Training:
10851         SetTrainingModeOff();
10852         break;
10853       case MachinePlaysWhite:
10854       case MachinePlaysBlack:
10855       case BeginningOfGame:
10856         SendToProgram("force\n", &first);
10857         SetUserThinkingEnables();
10858         break;
10859       case PlayFromGameFile:
10860         (void) StopLoadGameTimer();
10861         if (gameFileFP != NULL) {
10862             gameFileFP = NULL;
10863         }
10864         break;
10865       case EditPosition:
10866         EditPositionDone();
10867         break;
10868       case AnalyzeMode:
10869       case AnalyzeFile:
10870         ExitAnalyzeMode();
10871         SendToProgram("force\n", &first);
10872         break;
10873       case TwoMachinesPlay:
10874         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10875         ResurrectChessProgram();
10876         SetUserThinkingEnables();
10877         break;
10878       case EndOfGame:
10879         ResurrectChessProgram();
10880         break;
10881       case IcsPlayingBlack:
10882       case IcsPlayingWhite:
10883         DisplayError(_("Warning: You are still playing a game"), 0);
10884         break;
10885       case IcsObserving:
10886         DisplayError(_("Warning: You are still observing a game"), 0);
10887         break;
10888       case IcsExamining:
10889         DisplayError(_("Warning: You are still examining a game"), 0);
10890         break;
10891       case IcsIdle:
10892         break;
10893       case EditGame:
10894       default:
10895         return;
10896     }
10897     
10898     pausing = FALSE;
10899     StopClocks();
10900     first.offeredDraw = second.offeredDraw = 0;
10901
10902     if (gameMode == PlayFromGameFile) {
10903         whiteTimeRemaining = timeRemaining[0][currentMove];
10904         blackTimeRemaining = timeRemaining[1][currentMove];
10905         DisplayTitle("");
10906     }
10907
10908     if (gameMode == MachinePlaysWhite ||
10909         gameMode == MachinePlaysBlack ||
10910         gameMode == TwoMachinesPlay ||
10911         gameMode == EndOfGame) {
10912         i = forwardMostMove;
10913         while (i > currentMove) {
10914             SendToProgram("undo\n", &first);
10915             i--;
10916         }
10917         whiteTimeRemaining = timeRemaining[0][currentMove];
10918         blackTimeRemaining = timeRemaining[1][currentMove];
10919         DisplayBothClocks();
10920         if (whiteFlag || blackFlag) {
10921             whiteFlag = blackFlag = 0;
10922         }
10923         DisplayTitle("");
10924     }           
10925     
10926     gameMode = EditGame;
10927     ModeHighlight();
10928     SetGameInfo();
10929 }
10930
10931
10932 void
10933 EditPositionEvent()
10934 {
10935     if (gameMode == EditPosition) {
10936         EditGameEvent();
10937         return;
10938     }
10939     
10940     EditGameEvent();
10941     if (gameMode != EditGame) return;
10942     
10943     gameMode = EditPosition;
10944     ModeHighlight();
10945     SetGameInfo();
10946     if (currentMove > 0)
10947       CopyBoard(boards[0], boards[currentMove]);
10948     
10949     blackPlaysFirst = !WhiteOnMove(currentMove);
10950     ResetClocks();
10951     currentMove = forwardMostMove = backwardMostMove = 0;
10952     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10953     DisplayMove(-1);
10954 }
10955
10956 void
10957 ExitAnalyzeMode()
10958 {
10959     /* [DM] icsEngineAnalyze - possible call from other functions */
10960     if (appData.icsEngineAnalyze) {
10961         appData.icsEngineAnalyze = FALSE;
10962
10963         DisplayMessage("",_("Close ICS engine analyze..."));
10964     }
10965     if (first.analysisSupport && first.analyzing) {
10966       SendToProgram("exit\n", &first);
10967       first.analyzing = FALSE;
10968     }
10969     AnalysisPopDown();
10970     thinkOutput[0] = NULLCHAR;
10971 }
10972
10973 void
10974 EditPositionDone()
10975 {
10976     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
10977
10978     startedFromSetupPosition = TRUE;
10979     InitChessProgram(&first, FALSE);
10980     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
10981     if(boards[0][0][BOARD_WIDTH>>1] == king) {
10982         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
10983         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
10984     } else castlingRights[0][2] = -1;
10985     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
10986         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
10987         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
10988     } else castlingRights[0][5] = -1;
10989     SendToProgram("force\n", &first);
10990     if (blackPlaysFirst) {
10991         strcpy(moveList[0], "");
10992         strcpy(parseList[0], "");
10993         currentMove = forwardMostMove = backwardMostMove = 1;
10994         CopyBoard(boards[1], boards[0]);
10995         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10996         { int i;
10997           epStatus[1] = epStatus[0];
10998           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10999         }
11000     } else {
11001         currentMove = forwardMostMove = backwardMostMove = 0;
11002     }
11003     SendBoard(&first, forwardMostMove);
11004     if (appData.debugMode) {
11005         fprintf(debugFP, "EditPosDone\n");
11006     }
11007     DisplayTitle("");
11008     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11009     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11010     gameMode = EditGame;
11011     ModeHighlight();
11012     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11013     ClearHighlights(); /* [AS] */
11014 }
11015
11016 /* Pause for `ms' milliseconds */
11017 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11018 void
11019 TimeDelay(ms)
11020      long ms;
11021 {
11022     TimeMark m1, m2;
11023
11024     GetTimeMark(&m1);
11025     do {
11026         GetTimeMark(&m2);
11027     } while (SubtractTimeMarks(&m2, &m1) < ms);
11028 }
11029
11030 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11031 void
11032 SendMultiLineToICS(buf)
11033      char *buf;
11034 {
11035     char temp[MSG_SIZ+1], *p;
11036     int len;
11037
11038     len = strlen(buf);
11039     if (len > MSG_SIZ)
11040       len = MSG_SIZ;
11041   
11042     strncpy(temp, buf, len);
11043     temp[len] = 0;
11044
11045     p = temp;
11046     while (*p) {
11047         if (*p == '\n' || *p == '\r')
11048           *p = ' ';
11049         ++p;
11050     }
11051
11052     strcat(temp, "\n");
11053     SendToICS(temp);
11054     SendToPlayer(temp, strlen(temp));
11055 }
11056
11057 void
11058 SetWhiteToPlayEvent()
11059 {
11060     if (gameMode == EditPosition) {
11061         blackPlaysFirst = FALSE;
11062         DisplayBothClocks();    /* works because currentMove is 0 */
11063     } else if (gameMode == IcsExamining) {
11064         SendToICS(ics_prefix);
11065         SendToICS("tomove white\n");
11066     }
11067 }
11068
11069 void
11070 SetBlackToPlayEvent()
11071 {
11072     if (gameMode == EditPosition) {
11073         blackPlaysFirst = TRUE;
11074         currentMove = 1;        /* kludge */
11075         DisplayBothClocks();
11076         currentMove = 0;
11077     } else if (gameMode == IcsExamining) {
11078         SendToICS(ics_prefix);
11079         SendToICS("tomove black\n");
11080     }
11081 }
11082
11083 void
11084 EditPositionMenuEvent(selection, x, y)
11085      ChessSquare selection;
11086      int x, y;
11087 {
11088     char buf[MSG_SIZ];
11089     ChessSquare piece = boards[0][y][x];
11090
11091     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11092
11093     switch (selection) {
11094       case ClearBoard:
11095         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11096             SendToICS(ics_prefix);
11097             SendToICS("bsetup clear\n");
11098         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11099             SendToICS(ics_prefix);
11100             SendToICS("clearboard\n");
11101         } else {
11102             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11103                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11104                 for (y = 0; y < BOARD_HEIGHT; y++) {
11105                     if (gameMode == IcsExamining) {
11106                         if (boards[currentMove][y][x] != EmptySquare) {
11107                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11108                                     AAA + x, ONE + y);
11109                             SendToICS(buf);
11110                         }
11111                     } else {
11112                         boards[0][y][x] = p;
11113                     }
11114                 }
11115             }
11116         }
11117         if (gameMode == EditPosition) {
11118             DrawPosition(FALSE, boards[0]);
11119         }
11120         break;
11121
11122       case WhitePlay:
11123         SetWhiteToPlayEvent();
11124         break;
11125
11126       case BlackPlay:
11127         SetBlackToPlayEvent();
11128         break;
11129
11130       case EmptySquare:
11131         if (gameMode == IcsExamining) {
11132             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11133             SendToICS(buf);
11134         } else {
11135             boards[0][y][x] = EmptySquare;
11136             DrawPosition(FALSE, boards[0]);
11137         }
11138         break;
11139
11140       case PromotePiece:
11141         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11142            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11143             selection = (ChessSquare) (PROMOTED piece);
11144         } else if(piece == EmptySquare) selection = WhiteSilver;
11145         else selection = (ChessSquare)((int)piece - 1);
11146         goto defaultlabel;
11147
11148       case DemotePiece:
11149         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11150            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11151             selection = (ChessSquare) (DEMOTED piece);
11152         } else if(piece == EmptySquare) selection = BlackSilver;
11153         else selection = (ChessSquare)((int)piece + 1);       
11154         goto defaultlabel;
11155
11156       case WhiteQueen:
11157       case BlackQueen:
11158         if(gameInfo.variant == VariantShatranj ||
11159            gameInfo.variant == VariantXiangqi  ||
11160            gameInfo.variant == VariantCourier    )
11161             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11162         goto defaultlabel;
11163
11164       case WhiteKing:
11165       case BlackKing:
11166         if(gameInfo.variant == VariantXiangqi)
11167             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11168         if(gameInfo.variant == VariantKnightmate)
11169             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11170       default:
11171         defaultlabel:
11172         if (gameMode == IcsExamining) {
11173             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11174                     PieceToChar(selection), AAA + x, ONE + y);
11175             SendToICS(buf);
11176         } else {
11177             boards[0][y][x] = selection;
11178             DrawPosition(FALSE, boards[0]);
11179         }
11180         break;
11181     }
11182 }
11183
11184
11185 void
11186 DropMenuEvent(selection, x, y)
11187      ChessSquare selection;
11188      int x, y;
11189 {
11190     ChessMove moveType;
11191
11192     switch (gameMode) {
11193       case IcsPlayingWhite:
11194       case MachinePlaysBlack:
11195         if (!WhiteOnMove(currentMove)) {
11196             DisplayMoveError(_("It is Black's turn"));
11197             return;
11198         }
11199         moveType = WhiteDrop;
11200         break;
11201       case IcsPlayingBlack:
11202       case MachinePlaysWhite:
11203         if (WhiteOnMove(currentMove)) {
11204             DisplayMoveError(_("It is White's turn"));
11205             return;
11206         }
11207         moveType = BlackDrop;
11208         break;
11209       case EditGame:
11210         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11211         break;
11212       default:
11213         return;
11214     }
11215
11216     if (moveType == BlackDrop && selection < BlackPawn) {
11217       selection = (ChessSquare) ((int) selection
11218                                  + (int) BlackPawn - (int) WhitePawn);
11219     }
11220     if (boards[currentMove][y][x] != EmptySquare) {
11221         DisplayMoveError(_("That square is occupied"));
11222         return;
11223     }
11224
11225     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11226 }
11227
11228 void
11229 AcceptEvent()
11230 {
11231     /* Accept a pending offer of any kind from opponent */
11232     
11233     if (appData.icsActive) {
11234         SendToICS(ics_prefix);
11235         SendToICS("accept\n");
11236     } else if (cmailMsgLoaded) {
11237         if (currentMove == cmailOldMove &&
11238             commentList[cmailOldMove] != NULL &&
11239             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11240                    "Black offers a draw" : "White offers a draw")) {
11241             TruncateGame();
11242             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11243             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11244         } else {
11245             DisplayError(_("There is no pending offer on this move"), 0);
11246             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11247         }
11248     } else {
11249         /* Not used for offers from chess program */
11250     }
11251 }
11252
11253 void
11254 DeclineEvent()
11255 {
11256     /* Decline a pending offer of any kind from opponent */
11257     
11258     if (appData.icsActive) {
11259         SendToICS(ics_prefix);
11260         SendToICS("decline\n");
11261     } else if (cmailMsgLoaded) {
11262         if (currentMove == cmailOldMove &&
11263             commentList[cmailOldMove] != NULL &&
11264             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11265                    "Black offers a draw" : "White offers a draw")) {
11266 #ifdef NOTDEF
11267             AppendComment(cmailOldMove, "Draw declined");
11268             DisplayComment(cmailOldMove - 1, "Draw declined");
11269 #endif /*NOTDEF*/
11270         } else {
11271             DisplayError(_("There is no pending offer on this move"), 0);
11272         }
11273     } else {
11274         /* Not used for offers from chess program */
11275     }
11276 }
11277
11278 void
11279 RematchEvent()
11280 {
11281     /* Issue ICS rematch command */
11282     if (appData.icsActive) {
11283         SendToICS(ics_prefix);
11284         SendToICS("rematch\n");
11285     }
11286 }
11287
11288 void
11289 CallFlagEvent()
11290 {
11291     /* Call your opponent's flag (claim a win on time) */
11292     if (appData.icsActive) {
11293         SendToICS(ics_prefix);
11294         SendToICS("flag\n");
11295     } else {
11296         switch (gameMode) {
11297           default:
11298             return;
11299           case MachinePlaysWhite:
11300             if (whiteFlag) {
11301                 if (blackFlag)
11302                   GameEnds(GameIsDrawn, "Both players ran out of time",
11303                            GE_PLAYER);
11304                 else
11305                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11306             } else {
11307                 DisplayError(_("Your opponent is not out of time"), 0);
11308             }
11309             break;
11310           case MachinePlaysBlack:
11311             if (blackFlag) {
11312                 if (whiteFlag)
11313                   GameEnds(GameIsDrawn, "Both players ran out of time",
11314                            GE_PLAYER);
11315                 else
11316                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11317             } else {
11318                 DisplayError(_("Your opponent is not out of time"), 0);
11319             }
11320             break;
11321         }
11322     }
11323 }
11324
11325 void
11326 DrawEvent()
11327 {
11328     /* Offer draw or accept pending draw offer from opponent */
11329     
11330     if (appData.icsActive) {
11331         /* Note: tournament rules require draw offers to be
11332            made after you make your move but before you punch
11333            your clock.  Currently ICS doesn't let you do that;
11334            instead, you immediately punch your clock after making
11335            a move, but you can offer a draw at any time. */
11336         
11337         SendToICS(ics_prefix);
11338         SendToICS("draw\n");
11339     } else if (cmailMsgLoaded) {
11340         if (currentMove == cmailOldMove &&
11341             commentList[cmailOldMove] != NULL &&
11342             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11343                    "Black offers a draw" : "White offers a draw")) {
11344             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11345             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11346         } else if (currentMove == cmailOldMove + 1) {
11347             char *offer = WhiteOnMove(cmailOldMove) ?
11348               "White offers a draw" : "Black offers a draw";
11349             AppendComment(currentMove, offer);
11350             DisplayComment(currentMove - 1, offer);
11351             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11352         } else {
11353             DisplayError(_("You must make your move before offering a draw"), 0);
11354             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11355         }
11356     } else if (first.offeredDraw) {
11357         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11358     } else {
11359         if (first.sendDrawOffers) {
11360             SendToProgram("draw\n", &first);
11361             userOfferedDraw = TRUE;
11362         }
11363     }
11364 }
11365
11366 void
11367 AdjournEvent()
11368 {
11369     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11370     
11371     if (appData.icsActive) {
11372         SendToICS(ics_prefix);
11373         SendToICS("adjourn\n");
11374     } else {
11375         /* Currently GNU Chess doesn't offer or accept Adjourns */
11376     }
11377 }
11378
11379
11380 void
11381 AbortEvent()
11382 {
11383     /* Offer Abort or accept pending Abort offer from opponent */
11384     
11385     if (appData.icsActive) {
11386         SendToICS(ics_prefix);
11387         SendToICS("abort\n");
11388     } else {
11389         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11390     }
11391 }
11392
11393 void
11394 ResignEvent()
11395 {
11396     /* Resign.  You can do this even if it's not your turn. */
11397     
11398     if (appData.icsActive) {
11399         SendToICS(ics_prefix);
11400         SendToICS("resign\n");
11401     } else {
11402         switch (gameMode) {
11403           case MachinePlaysWhite:
11404             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11405             break;
11406           case MachinePlaysBlack:
11407             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11408             break;
11409           case EditGame:
11410             if (cmailMsgLoaded) {
11411                 TruncateGame();
11412                 if (WhiteOnMove(cmailOldMove)) {
11413                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11414                 } else {
11415                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11416                 }
11417                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11418             }
11419             break;
11420           default:
11421             break;
11422         }
11423     }
11424 }
11425
11426
11427 void
11428 StopObservingEvent()
11429 {
11430     /* Stop observing current games */
11431     SendToICS(ics_prefix);
11432     SendToICS("unobserve\n");
11433 }
11434
11435 void
11436 StopExaminingEvent()
11437 {
11438     /* Stop observing current game */
11439     SendToICS(ics_prefix);
11440     SendToICS("unexamine\n");
11441 }
11442
11443 void
11444 ForwardInner(target)
11445      int target;
11446 {
11447     int limit;
11448
11449     if (appData.debugMode)
11450         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11451                 target, currentMove, forwardMostMove);
11452
11453     if (gameMode == EditPosition)
11454       return;
11455
11456     if (gameMode == PlayFromGameFile && !pausing)
11457       PauseEvent();
11458     
11459     if (gameMode == IcsExamining && pausing)
11460       limit = pauseExamForwardMostMove;
11461     else
11462       limit = forwardMostMove;
11463     
11464     if (target > limit) target = limit;
11465
11466     if (target > 0 && moveList[target - 1][0]) {
11467         int fromX, fromY, toX, toY;
11468         toX = moveList[target - 1][2] - AAA;
11469         toY = moveList[target - 1][3] - ONE;
11470         if (moveList[target - 1][1] == '@') {
11471             if (appData.highlightLastMove) {
11472                 SetHighlights(-1, -1, toX, toY);
11473             }
11474         } else {
11475             fromX = moveList[target - 1][0] - AAA;
11476             fromY = moveList[target - 1][1] - ONE;
11477             if (target == currentMove + 1) {
11478                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11479             }
11480             if (appData.highlightLastMove) {
11481                 SetHighlights(fromX, fromY, toX, toY);
11482             }
11483         }
11484     }
11485     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11486         gameMode == Training || gameMode == PlayFromGameFile || 
11487         gameMode == AnalyzeFile) {
11488         while (currentMove < target) {
11489             SendMoveToProgram(currentMove++, &first);
11490         }
11491     } else {
11492         currentMove = target;
11493     }
11494     
11495     if (gameMode == EditGame || gameMode == EndOfGame) {
11496         whiteTimeRemaining = timeRemaining[0][currentMove];
11497         blackTimeRemaining = timeRemaining[1][currentMove];
11498     }
11499     DisplayBothClocks();
11500     DisplayMove(currentMove - 1);
11501     DrawPosition(FALSE, boards[currentMove]);
11502     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11503     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11504         DisplayComment(currentMove - 1, commentList[currentMove]);
11505     }
11506 }
11507
11508
11509 void
11510 ForwardEvent()
11511 {
11512     if (gameMode == IcsExamining && !pausing) {
11513         SendToICS(ics_prefix);
11514         SendToICS("forward\n");
11515     } else {
11516         ForwardInner(currentMove + 1);
11517     }
11518 }
11519
11520 void
11521 ToEndEvent()
11522 {
11523     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11524         /* to optimze, we temporarily turn off analysis mode while we feed
11525          * the remaining moves to the engine. Otherwise we get analysis output
11526          * after each move.
11527          */ 
11528         if (first.analysisSupport) {
11529           SendToProgram("exit\nforce\n", &first);
11530           first.analyzing = FALSE;
11531         }
11532     }
11533         
11534     if (gameMode == IcsExamining && !pausing) {
11535         SendToICS(ics_prefix);
11536         SendToICS("forward 999999\n");
11537     } else {
11538         ForwardInner(forwardMostMove);
11539     }
11540
11541     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11542         /* we have fed all the moves, so reactivate analysis mode */
11543         SendToProgram("analyze\n", &first);
11544         first.analyzing = TRUE;
11545         /*first.maybeThinking = TRUE;*/
11546         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11547     }
11548 }
11549
11550 void
11551 BackwardInner(target)
11552      int target;
11553 {
11554     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11555
11556     if (appData.debugMode)
11557         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11558                 target, currentMove, forwardMostMove);
11559
11560     if (gameMode == EditPosition) return;
11561     if (currentMove <= backwardMostMove) {
11562         ClearHighlights();
11563         DrawPosition(full_redraw, boards[currentMove]);
11564         return;
11565     }
11566     if (gameMode == PlayFromGameFile && !pausing)
11567       PauseEvent();
11568     
11569     if (moveList[target][0]) {
11570         int fromX, fromY, toX, toY;
11571         toX = moveList[target][2] - AAA;
11572         toY = moveList[target][3] - ONE;
11573         if (moveList[target][1] == '@') {
11574             if (appData.highlightLastMove) {
11575                 SetHighlights(-1, -1, toX, toY);
11576             }
11577         } else {
11578             fromX = moveList[target][0] - AAA;
11579             fromY = moveList[target][1] - ONE;
11580             if (target == currentMove - 1) {
11581                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11582             }
11583             if (appData.highlightLastMove) {
11584                 SetHighlights(fromX, fromY, toX, toY);
11585             }
11586         }
11587     }
11588     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11589         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11590         while (currentMove > target) {
11591             SendToProgram("undo\n", &first);
11592             currentMove--;
11593         }
11594     } else {
11595         currentMove = target;
11596     }
11597     
11598     if (gameMode == EditGame || gameMode == EndOfGame) {
11599         whiteTimeRemaining = timeRemaining[0][currentMove];
11600         blackTimeRemaining = timeRemaining[1][currentMove];
11601     }
11602     DisplayBothClocks();
11603     DisplayMove(currentMove - 1);
11604     DrawPosition(full_redraw, boards[currentMove]);
11605     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11606     // [HGM] PV info: routine tests if comment empty
11607     DisplayComment(currentMove - 1, commentList[currentMove]);
11608 }
11609
11610 void
11611 BackwardEvent()
11612 {
11613     if (gameMode == IcsExamining && !pausing) {
11614         SendToICS(ics_prefix);
11615         SendToICS("backward\n");
11616     } else {
11617         BackwardInner(currentMove - 1);
11618     }
11619 }
11620
11621 void
11622 ToStartEvent()
11623 {
11624     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11625         /* to optimze, we temporarily turn off analysis mode while we undo
11626          * all the moves. Otherwise we get analysis output after each undo.
11627          */ 
11628         if (first.analysisSupport) {
11629           SendToProgram("exit\nforce\n", &first);
11630           first.analyzing = FALSE;
11631         }
11632     }
11633
11634     if (gameMode == IcsExamining && !pausing) {
11635         SendToICS(ics_prefix);
11636         SendToICS("backward 999999\n");
11637     } else {
11638         BackwardInner(backwardMostMove);
11639     }
11640
11641     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11642         /* we have fed all the moves, so reactivate analysis mode */
11643         SendToProgram("analyze\n", &first);
11644         first.analyzing = TRUE;
11645         /*first.maybeThinking = TRUE;*/
11646         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11647     }
11648 }
11649
11650 void
11651 ToNrEvent(int to)
11652 {
11653   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11654   if (to >= forwardMostMove) to = forwardMostMove;
11655   if (to <= backwardMostMove) to = backwardMostMove;
11656   if (to < currentMove) {
11657     BackwardInner(to);
11658   } else {
11659     ForwardInner(to);
11660   }
11661 }
11662
11663 void
11664 RevertEvent()
11665 {
11666     if (gameMode != IcsExamining) {
11667         DisplayError(_("You are not examining a game"), 0);
11668         return;
11669     }
11670     if (pausing) {
11671         DisplayError(_("You can't revert while pausing"), 0);
11672         return;
11673     }
11674     SendToICS(ics_prefix);
11675     SendToICS("revert\n");
11676 }
11677
11678 void
11679 RetractMoveEvent()
11680 {
11681     switch (gameMode) {
11682       case MachinePlaysWhite:
11683       case MachinePlaysBlack:
11684         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11685             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11686             return;
11687         }
11688         if (forwardMostMove < 2) return;
11689         currentMove = forwardMostMove = forwardMostMove - 2;
11690         whiteTimeRemaining = timeRemaining[0][currentMove];
11691         blackTimeRemaining = timeRemaining[1][currentMove];
11692         DisplayBothClocks();
11693         DisplayMove(currentMove - 1);
11694         ClearHighlights();/*!! could figure this out*/
11695         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11696         SendToProgram("remove\n", &first);
11697         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11698         break;
11699
11700       case BeginningOfGame:
11701       default:
11702         break;
11703
11704       case IcsPlayingWhite:
11705       case IcsPlayingBlack:
11706         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11707             SendToICS(ics_prefix);
11708             SendToICS("takeback 2\n");
11709         } else {
11710             SendToICS(ics_prefix);
11711             SendToICS("takeback 1\n");
11712         }
11713         break;
11714     }
11715 }
11716
11717 void
11718 MoveNowEvent()
11719 {
11720     ChessProgramState *cps;
11721
11722     switch (gameMode) {
11723       case MachinePlaysWhite:
11724         if (!WhiteOnMove(forwardMostMove)) {
11725             DisplayError(_("It is your turn"), 0);
11726             return;
11727         }
11728         cps = &first;
11729         break;
11730       case MachinePlaysBlack:
11731         if (WhiteOnMove(forwardMostMove)) {
11732             DisplayError(_("It is your turn"), 0);
11733             return;
11734         }
11735         cps = &first;
11736         break;
11737       case TwoMachinesPlay:
11738         if (WhiteOnMove(forwardMostMove) ==
11739             (first.twoMachinesColor[0] == 'w')) {
11740             cps = &first;
11741         } else {
11742             cps = &second;
11743         }
11744         break;
11745       case BeginningOfGame:
11746       default:
11747         return;
11748     }
11749     SendToProgram("?\n", cps);
11750 }
11751
11752 void
11753 TruncateGameEvent()
11754 {
11755     EditGameEvent();
11756     if (gameMode != EditGame) return;
11757     TruncateGame();
11758 }
11759
11760 void
11761 TruncateGame()
11762 {
11763     if (forwardMostMove > currentMove) {
11764         if (gameInfo.resultDetails != NULL) {
11765             free(gameInfo.resultDetails);
11766             gameInfo.resultDetails = NULL;
11767             gameInfo.result = GameUnfinished;
11768         }
11769         forwardMostMove = currentMove;
11770         HistorySet(parseList, backwardMostMove, forwardMostMove,
11771                    currentMove-1);
11772     }
11773 }
11774
11775 void
11776 HintEvent()
11777 {
11778     if (appData.noChessProgram) return;
11779     switch (gameMode) {
11780       case MachinePlaysWhite:
11781         if (WhiteOnMove(forwardMostMove)) {
11782             DisplayError(_("Wait until your turn"), 0);
11783             return;
11784         }
11785         break;
11786       case BeginningOfGame:
11787       case MachinePlaysBlack:
11788         if (!WhiteOnMove(forwardMostMove)) {
11789             DisplayError(_("Wait until your turn"), 0);
11790             return;
11791         }
11792         break;
11793       default:
11794         DisplayError(_("No hint available"), 0);
11795         return;
11796     }
11797     SendToProgram("hint\n", &first);
11798     hintRequested = TRUE;
11799 }
11800
11801 void
11802 BookEvent()
11803 {
11804     if (appData.noChessProgram) return;
11805     switch (gameMode) {
11806       case MachinePlaysWhite:
11807         if (WhiteOnMove(forwardMostMove)) {
11808             DisplayError(_("Wait until your turn"), 0);
11809             return;
11810         }
11811         break;
11812       case BeginningOfGame:
11813       case MachinePlaysBlack:
11814         if (!WhiteOnMove(forwardMostMove)) {
11815             DisplayError(_("Wait until your turn"), 0);
11816             return;
11817         }
11818         break;
11819       case EditPosition:
11820         EditPositionDone();
11821         break;
11822       case TwoMachinesPlay:
11823         return;
11824       default:
11825         break;
11826     }
11827     SendToProgram("bk\n", &first);
11828     bookOutput[0] = NULLCHAR;
11829     bookRequested = TRUE;
11830 }
11831
11832 void
11833 AboutGameEvent()
11834 {
11835     char *tags = PGNTags(&gameInfo);
11836     TagsPopUp(tags, CmailMsg());
11837     free(tags);
11838 }
11839
11840 /* end button procedures */
11841
11842 void
11843 PrintPosition(fp, move)
11844      FILE *fp;
11845      int move;
11846 {
11847     int i, j;
11848     
11849     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11850         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11851             char c = PieceToChar(boards[move][i][j]);
11852             fputc(c == 'x' ? '.' : c, fp);
11853             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11854         }
11855     }
11856     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11857       fprintf(fp, "white to play\n");
11858     else
11859       fprintf(fp, "black to play\n");
11860 }
11861
11862 void
11863 PrintOpponents(fp)
11864      FILE *fp;
11865 {
11866     if (gameInfo.white != NULL) {
11867         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11868     } else {
11869         fprintf(fp, "\n");
11870     }
11871 }
11872
11873 /* Find last component of program's own name, using some heuristics */
11874 void
11875 TidyProgramName(prog, host, buf)
11876      char *prog, *host, buf[MSG_SIZ];
11877 {
11878     char *p, *q;
11879     int local = (strcmp(host, "localhost") == 0);
11880     while (!local && (p = strchr(prog, ';')) != NULL) {
11881         p++;
11882         while (*p == ' ') p++;
11883         prog = p;
11884     }
11885     if (*prog == '"' || *prog == '\'') {
11886         q = strchr(prog + 1, *prog);
11887     } else {
11888         q = strchr(prog, ' ');
11889     }
11890     if (q == NULL) q = prog + strlen(prog);
11891     p = q;
11892     while (p >= prog && *p != '/' && *p != '\\') p--;
11893     p++;
11894     if(p == prog && *p == '"') p++;
11895     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11896     memcpy(buf, p, q - p);
11897     buf[q - p] = NULLCHAR;
11898     if (!local) {
11899         strcat(buf, "@");
11900         strcat(buf, host);
11901     }
11902 }
11903
11904 char *
11905 TimeControlTagValue()
11906 {
11907     char buf[MSG_SIZ];
11908     if (!appData.clockMode) {
11909         strcpy(buf, "-");
11910     } else if (movesPerSession > 0) {
11911         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11912     } else if (timeIncrement == 0) {
11913         sprintf(buf, "%ld", timeControl/1000);
11914     } else {
11915         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11916     }
11917     return StrSave(buf);
11918 }
11919
11920 void
11921 SetGameInfo()
11922 {
11923     /* This routine is used only for certain modes */
11924     VariantClass v = gameInfo.variant;
11925     ClearGameInfo(&gameInfo);
11926     gameInfo.variant = v;
11927
11928     switch (gameMode) {
11929       case MachinePlaysWhite:
11930         gameInfo.event = StrSave( appData.pgnEventHeader );
11931         gameInfo.site = StrSave(HostName());
11932         gameInfo.date = PGNDate();
11933         gameInfo.round = StrSave("-");
11934         gameInfo.white = StrSave(first.tidy);
11935         gameInfo.black = StrSave(UserName());
11936         gameInfo.timeControl = TimeControlTagValue();
11937         break;
11938
11939       case MachinePlaysBlack:
11940         gameInfo.event = StrSave( appData.pgnEventHeader );
11941         gameInfo.site = StrSave(HostName());
11942         gameInfo.date = PGNDate();
11943         gameInfo.round = StrSave("-");
11944         gameInfo.white = StrSave(UserName());
11945         gameInfo.black = StrSave(first.tidy);
11946         gameInfo.timeControl = TimeControlTagValue();
11947         break;
11948
11949       case TwoMachinesPlay:
11950         gameInfo.event = StrSave( appData.pgnEventHeader );
11951         gameInfo.site = StrSave(HostName());
11952         gameInfo.date = PGNDate();
11953         if (matchGame > 0) {
11954             char buf[MSG_SIZ];
11955             sprintf(buf, "%d", matchGame);
11956             gameInfo.round = StrSave(buf);
11957         } else {
11958             gameInfo.round = StrSave("-");
11959         }
11960         if (first.twoMachinesColor[0] == 'w') {
11961             gameInfo.white = StrSave(first.tidy);
11962             gameInfo.black = StrSave(second.tidy);
11963         } else {
11964             gameInfo.white = StrSave(second.tidy);
11965             gameInfo.black = StrSave(first.tidy);
11966         }
11967         gameInfo.timeControl = TimeControlTagValue();
11968         break;
11969
11970       case EditGame:
11971         gameInfo.event = StrSave("Edited game");
11972         gameInfo.site = StrSave(HostName());
11973         gameInfo.date = PGNDate();
11974         gameInfo.round = StrSave("-");
11975         gameInfo.white = StrSave("-");
11976         gameInfo.black = StrSave("-");
11977         break;
11978
11979       case EditPosition:
11980         gameInfo.event = StrSave("Edited position");
11981         gameInfo.site = StrSave(HostName());
11982         gameInfo.date = PGNDate();
11983         gameInfo.round = StrSave("-");
11984         gameInfo.white = StrSave("-");
11985         gameInfo.black = StrSave("-");
11986         break;
11987
11988       case IcsPlayingWhite:
11989       case IcsPlayingBlack:
11990       case IcsObserving:
11991       case IcsExamining:
11992         break;
11993
11994       case PlayFromGameFile:
11995         gameInfo.event = StrSave("Game from non-PGN file");
11996         gameInfo.site = StrSave(HostName());
11997         gameInfo.date = PGNDate();
11998         gameInfo.round = StrSave("-");
11999         gameInfo.white = StrSave("?");
12000         gameInfo.black = StrSave("?");
12001         break;
12002
12003       default:
12004         break;
12005     }
12006 }
12007
12008 void
12009 ReplaceComment(index, text)
12010      int index;
12011      char *text;
12012 {
12013     int len;
12014
12015     while (*text == '\n') text++;
12016     len = strlen(text);
12017     while (len > 0 && text[len - 1] == '\n') len--;
12018
12019     if (commentList[index] != NULL)
12020       free(commentList[index]);
12021
12022     if (len == 0) {
12023         commentList[index] = NULL;
12024         return;
12025     }
12026     commentList[index] = (char *) malloc(len + 2);
12027     strncpy(commentList[index], text, len);
12028     commentList[index][len] = '\n';
12029     commentList[index][len + 1] = NULLCHAR;
12030 }
12031
12032 void
12033 CrushCRs(text)
12034      char *text;
12035 {
12036   char *p = text;
12037   char *q = text;
12038   char ch;
12039
12040   do {
12041     ch = *p++;
12042     if (ch == '\r') continue;
12043     *q++ = ch;
12044   } while (ch != '\0');
12045 }
12046
12047 void
12048 AppendComment(index, text)
12049      int index;
12050      char *text;
12051 {
12052     int oldlen, len;
12053     char *old;
12054
12055     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12056
12057     CrushCRs(text);
12058     while (*text == '\n') text++;
12059     len = strlen(text);
12060     while (len > 0 && text[len - 1] == '\n') len--;
12061
12062     if (len == 0) return;
12063
12064     if (commentList[index] != NULL) {
12065         old = commentList[index];
12066         oldlen = strlen(old);
12067         commentList[index] = (char *) malloc(oldlen + len + 2);
12068         strcpy(commentList[index], old);
12069         free(old);
12070         strncpy(&commentList[index][oldlen], text, len);
12071         commentList[index][oldlen + len] = '\n';
12072         commentList[index][oldlen + len + 1] = NULLCHAR;
12073     } else {
12074         commentList[index] = (char *) malloc(len + 2);
12075         strncpy(commentList[index], text, len);
12076         commentList[index][len] = '\n';
12077         commentList[index][len + 1] = NULLCHAR;
12078     }
12079 }
12080
12081 static char * FindStr( char * text, char * sub_text )
12082 {
12083     char * result = strstr( text, sub_text );
12084
12085     if( result != NULL ) {
12086         result += strlen( sub_text );
12087     }
12088
12089     return result;
12090 }
12091
12092 /* [AS] Try to extract PV info from PGN comment */
12093 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12094 char *GetInfoFromComment( int index, char * text )
12095 {
12096     char * sep = text;
12097
12098     if( text != NULL && index > 0 ) {
12099         int score = 0;
12100         int depth = 0;
12101         int time = -1, sec = 0, deci;
12102         char * s_eval = FindStr( text, "[%eval " );
12103         char * s_emt = FindStr( text, "[%emt " );
12104
12105         if( s_eval != NULL || s_emt != NULL ) {
12106             /* New style */
12107             char delim;
12108
12109             if( s_eval != NULL ) {
12110                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12111                     return text;
12112                 }
12113
12114                 if( delim != ']' ) {
12115                     return text;
12116                 }
12117             }
12118
12119             if( s_emt != NULL ) {
12120             }
12121         }
12122         else {
12123             /* We expect something like: [+|-]nnn.nn/dd */
12124             int score_lo = 0;
12125
12126             sep = strchr( text, '/' );
12127             if( sep == NULL || sep < (text+4) ) {
12128                 return text;
12129             }
12130
12131             time = -1; sec = -1; deci = -1;
12132             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12133                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12134                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12135                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12136                 return text;
12137             }
12138
12139             if( score_lo < 0 || score_lo >= 100 ) {
12140                 return text;
12141             }
12142
12143             if(sec >= 0) time = 600*time + 10*sec; else
12144             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12145
12146             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12147
12148             /* [HGM] PV time: now locate end of PV info */
12149             while( *++sep >= '0' && *sep <= '9'); // strip depth
12150             if(time >= 0)
12151             while( *++sep >= '0' && *sep <= '9'); // strip time
12152             if(sec >= 0)
12153             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12154             if(deci >= 0)
12155             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12156             while(*sep == ' ') sep++;
12157         }
12158
12159         if( depth <= 0 ) {
12160             return text;
12161         }
12162
12163         if( time < 0 ) {
12164             time = -1;
12165         }
12166
12167         pvInfoList[index-1].depth = depth;
12168         pvInfoList[index-1].score = score;
12169         pvInfoList[index-1].time  = 10*time; // centi-sec
12170     }
12171     return sep;
12172 }
12173
12174 void
12175 SendToProgram(message, cps)
12176      char *message;
12177      ChessProgramState *cps;
12178 {
12179     int count, outCount, error;
12180     char buf[MSG_SIZ];
12181
12182     if (cps->pr == NULL) return;
12183     Attention(cps);
12184     
12185     if (appData.debugMode) {
12186         TimeMark now;
12187         GetTimeMark(&now);
12188         fprintf(debugFP, "%ld >%-6s: %s", 
12189                 SubtractTimeMarks(&now, &programStartTime),
12190                 cps->which, message);
12191     }
12192     
12193     count = strlen(message);
12194     outCount = OutputToProcess(cps->pr, message, count, &error);
12195     if (outCount < count && !exiting 
12196                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12197         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12198         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12199             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12200                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12201                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12202             } else {
12203                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12204             }
12205             gameInfo.resultDetails = buf;
12206         }
12207         DisplayFatalError(buf, error, 1);
12208     }
12209 }
12210
12211 void
12212 ReceiveFromProgram(isr, closure, message, count, error)
12213      InputSourceRef isr;
12214      VOIDSTAR closure;
12215      char *message;
12216      int count;
12217      int error;
12218 {
12219     char *end_str;
12220     char buf[MSG_SIZ];
12221     ChessProgramState *cps = (ChessProgramState *)closure;
12222
12223     if (isr != cps->isr) return; /* Killed intentionally */
12224     if (count <= 0) {
12225         if (count == 0) {
12226             sprintf(buf,
12227                     _("Error: %s chess program (%s) exited unexpectedly"),
12228                     cps->which, cps->program);
12229         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12230                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12231                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12232                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12233                 } else {
12234                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12235                 }
12236                 gameInfo.resultDetails = buf;
12237             }
12238             RemoveInputSource(cps->isr);
12239             DisplayFatalError(buf, 0, 1);
12240         } else {
12241             sprintf(buf,
12242                     _("Error reading from %s chess program (%s)"),
12243                     cps->which, cps->program);
12244             RemoveInputSource(cps->isr);
12245
12246             /* [AS] Program is misbehaving badly... kill it */
12247             if( count == -2 ) {
12248                 DestroyChildProcess( cps->pr, 9 );
12249                 cps->pr = NoProc;
12250             }
12251
12252             DisplayFatalError(buf, error, 1);
12253         }
12254         return;
12255     }
12256     
12257     if ((end_str = strchr(message, '\r')) != NULL)
12258       *end_str = NULLCHAR;
12259     if ((end_str = strchr(message, '\n')) != NULL)
12260       *end_str = NULLCHAR;
12261     
12262     if (appData.debugMode) {
12263         TimeMark now; int print = 1;
12264         char *quote = ""; char c; int i;
12265
12266         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12267                 char start = message[0];
12268                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12269                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12270                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12271                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12272                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12273                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12274                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12275                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12276                         { quote = "# "; print = (appData.engineComments == 2); }
12277                 message[0] = start; // restore original message
12278         }
12279         if(print) {
12280                 GetTimeMark(&now);
12281                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12282                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12283                         quote,
12284                         message);
12285         }
12286     }
12287
12288     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12289     if (appData.icsEngineAnalyze) {
12290         if (strstr(message, "whisper") != NULL ||
12291              strstr(message, "kibitz") != NULL || 
12292             strstr(message, "tellics") != NULL) return;
12293     }
12294
12295     HandleMachineMove(message, cps);
12296 }
12297
12298
12299 void
12300 SendTimeControl(cps, mps, tc, inc, sd, st)
12301      ChessProgramState *cps;
12302      int mps, inc, sd, st;
12303      long tc;
12304 {
12305     char buf[MSG_SIZ];
12306     int seconds;
12307
12308     if( timeControl_2 > 0 ) {
12309         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12310             tc = timeControl_2;
12311         }
12312     }
12313     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12314     inc /= cps->timeOdds;
12315     st  /= cps->timeOdds;
12316
12317     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12318
12319     if (st > 0) {
12320       /* Set exact time per move, normally using st command */
12321       if (cps->stKludge) {
12322         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12323         seconds = st % 60;
12324         if (seconds == 0) {
12325           sprintf(buf, "level 1 %d\n", st/60);
12326         } else {
12327           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12328         }
12329       } else {
12330         sprintf(buf, "st %d\n", st);
12331       }
12332     } else {
12333       /* Set conventional or incremental time control, using level command */
12334       if (seconds == 0) {
12335         /* Note old gnuchess bug -- minutes:seconds used to not work.
12336            Fixed in later versions, but still avoid :seconds
12337            when seconds is 0. */
12338         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12339       } else {
12340         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12341                 seconds, inc/1000);
12342       }
12343     }
12344     SendToProgram(buf, cps);
12345
12346     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12347     /* Orthogonally, limit search to given depth */
12348     if (sd > 0) {
12349       if (cps->sdKludge) {
12350         sprintf(buf, "depth\n%d\n", sd);
12351       } else {
12352         sprintf(buf, "sd %d\n", sd);
12353       }
12354       SendToProgram(buf, cps);
12355     }
12356
12357     if(cps->nps > 0) { /* [HGM] nps */
12358         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12359         else {
12360                 sprintf(buf, "nps %d\n", cps->nps);
12361               SendToProgram(buf, cps);
12362         }
12363     }
12364 }
12365
12366 ChessProgramState *WhitePlayer()
12367 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12368 {
12369     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12370        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12371         return &second;
12372     return &first;
12373 }
12374
12375 void
12376 SendTimeRemaining(cps, machineWhite)
12377      ChessProgramState *cps;
12378      int /*boolean*/ machineWhite;
12379 {
12380     char message[MSG_SIZ];
12381     long time, otime;
12382
12383     /* Note: this routine must be called when the clocks are stopped
12384        or when they have *just* been set or switched; otherwise
12385        it will be off by the time since the current tick started.
12386     */
12387     if (machineWhite) {
12388         time = whiteTimeRemaining / 10;
12389         otime = blackTimeRemaining / 10;
12390     } else {
12391         time = blackTimeRemaining / 10;
12392         otime = whiteTimeRemaining / 10;
12393     }
12394     /* [HGM] translate opponent's time by time-odds factor */
12395     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12396     if (appData.debugMode) {
12397         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12398     }
12399
12400     if (time <= 0) time = 1;
12401     if (otime <= 0) otime = 1;
12402     
12403     sprintf(message, "time %ld\n", time);
12404     SendToProgram(message, cps);
12405
12406     sprintf(message, "otim %ld\n", otime);
12407     SendToProgram(message, cps);
12408 }
12409
12410 int
12411 BoolFeature(p, name, loc, cps)
12412      char **p;
12413      char *name;
12414      int *loc;
12415      ChessProgramState *cps;
12416 {
12417   char buf[MSG_SIZ];
12418   int len = strlen(name);
12419   int val;
12420   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12421     (*p) += len + 1;
12422     sscanf(*p, "%d", &val);
12423     *loc = (val != 0);
12424     while (**p && **p != ' ') (*p)++;
12425     sprintf(buf, "accepted %s\n", name);
12426     SendToProgram(buf, cps);
12427     return TRUE;
12428   }
12429   return FALSE;
12430 }
12431
12432 int
12433 IntFeature(p, name, loc, cps)
12434      char **p;
12435      char *name;
12436      int *loc;
12437      ChessProgramState *cps;
12438 {
12439   char buf[MSG_SIZ];
12440   int len = strlen(name);
12441   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12442     (*p) += len + 1;
12443     sscanf(*p, "%d", loc);
12444     while (**p && **p != ' ') (*p)++;
12445     sprintf(buf, "accepted %s\n", name);
12446     SendToProgram(buf, cps);
12447     return TRUE;
12448   }
12449   return FALSE;
12450 }
12451
12452 int
12453 StringFeature(p, name, loc, cps)
12454      char **p;
12455      char *name;
12456      char loc[];
12457      ChessProgramState *cps;
12458 {
12459   char buf[MSG_SIZ];
12460   int len = strlen(name);
12461   if (strncmp((*p), name, len) == 0
12462       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12463     (*p) += len + 2;
12464     sscanf(*p, "%[^\"]", loc);
12465     while (**p && **p != '\"') (*p)++;
12466     if (**p == '\"') (*p)++;
12467     sprintf(buf, "accepted %s\n", name);
12468     SendToProgram(buf, cps);
12469     return TRUE;
12470   }
12471   return FALSE;
12472 }
12473
12474 int 
12475 ParseOption(Option *opt, ChessProgramState *cps)
12476 // [HGM] options: process the string that defines an engine option, and determine
12477 // name, type, default value, and allowed value range
12478 {
12479         char *p, *q, buf[MSG_SIZ];
12480         int n, min = (-1)<<31, max = 1<<31, def;
12481
12482         if(p = strstr(opt->name, " -spin ")) {
12483             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12484             if(max < min) max = min; // enforce consistency
12485             if(def < min) def = min;
12486             if(def > max) def = max;
12487             opt->value = def;
12488             opt->min = min;
12489             opt->max = max;
12490             opt->type = Spin;
12491         } else if((p = strstr(opt->name, " -slider "))) {
12492             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12493             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12494             if(max < min) max = min; // enforce consistency
12495             if(def < min) def = min;
12496             if(def > max) def = max;
12497             opt->value = def;
12498             opt->min = min;
12499             opt->max = max;
12500             opt->type = Spin; // Slider;
12501         } else if((p = strstr(opt->name, " -string "))) {
12502             opt->textValue = p+9;
12503             opt->type = TextBox;
12504         } else if((p = strstr(opt->name, " -file "))) {
12505             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12506             opt->textValue = p+7;
12507             opt->type = TextBox; // FileName;
12508         } else if((p = strstr(opt->name, " -path "))) {
12509             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12510             opt->textValue = p+7;
12511             opt->type = TextBox; // PathName;
12512         } else if(p = strstr(opt->name, " -check ")) {
12513             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12514             opt->value = (def != 0);
12515             opt->type = CheckBox;
12516         } else if(p = strstr(opt->name, " -combo ")) {
12517             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12518             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12519             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12520             opt->value = n = 0;
12521             while(q = StrStr(q, " /// ")) {
12522                 n++; *q = 0;    // count choices, and null-terminate each of them
12523                 q += 5;
12524                 if(*q == '*') { // remember default, which is marked with * prefix
12525                     q++;
12526                     opt->value = n;
12527                 }
12528                 cps->comboList[cps->comboCnt++] = q;
12529             }
12530             cps->comboList[cps->comboCnt++] = NULL;
12531             opt->max = n + 1;
12532             opt->type = ComboBox;
12533         } else if(p = strstr(opt->name, " -button")) {
12534             opt->type = Button;
12535         } else if(p = strstr(opt->name, " -save")) {
12536             opt->type = SaveButton;
12537         } else return FALSE;
12538         *p = 0; // terminate option name
12539         // now look if the command-line options define a setting for this engine option.
12540         if(cps->optionSettings && cps->optionSettings[0])
12541             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12542         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12543                 sprintf(buf, "option %s", p);
12544                 if(p = strstr(buf, ",")) *p = 0;
12545                 strcat(buf, "\n");
12546                 SendToProgram(buf, cps);
12547         }
12548         return TRUE;
12549 }
12550
12551 void
12552 FeatureDone(cps, val)
12553      ChessProgramState* cps;
12554      int val;
12555 {
12556   DelayedEventCallback cb = GetDelayedEvent();
12557   if ((cb == InitBackEnd3 && cps == &first) ||
12558       (cb == TwoMachinesEventIfReady && cps == &second)) {
12559     CancelDelayedEvent();
12560     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12561   }
12562   cps->initDone = val;
12563 }
12564
12565 /* Parse feature command from engine */
12566 void
12567 ParseFeatures(args, cps)
12568      char* args;
12569      ChessProgramState *cps;  
12570 {
12571   char *p = args;
12572   char *q;
12573   int val;
12574   char buf[MSG_SIZ];
12575
12576   for (;;) {
12577     while (*p == ' ') p++;
12578     if (*p == NULLCHAR) return;
12579
12580     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12581     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12582     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12583     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12584     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12585     if (BoolFeature(&p, "reuse", &val, cps)) {
12586       /* Engine can disable reuse, but can't enable it if user said no */
12587       if (!val) cps->reuse = FALSE;
12588       continue;
12589     }
12590     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12591     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12592       if (gameMode == TwoMachinesPlay) {
12593         DisplayTwoMachinesTitle();
12594       } else {
12595         DisplayTitle("");
12596       }
12597       continue;
12598     }
12599     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12600     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12601     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12602     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12603     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12604     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12605     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12606     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12607     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12608     if (IntFeature(&p, "done", &val, cps)) {
12609       FeatureDone(cps, val);
12610       continue;
12611     }
12612     /* Added by Tord: */
12613     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12614     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12615     /* End of additions by Tord */
12616
12617     /* [HGM] added features: */
12618     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12619     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12620     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12621     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12622     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12623     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12624     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12625         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12626             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12627             SendToProgram(buf, cps);
12628             continue;
12629         }
12630         if(cps->nrOptions >= MAX_OPTIONS) {
12631             cps->nrOptions--;
12632             sprintf(buf, "%s engine has too many options\n", cps->which);
12633             DisplayError(buf, 0);
12634         }
12635         continue;
12636     }
12637     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12638     /* End of additions by HGM */
12639
12640     /* unknown feature: complain and skip */
12641     q = p;
12642     while (*q && *q != '=') q++;
12643     sprintf(buf, "rejected %.*s\n", q-p, p);
12644     SendToProgram(buf, cps);
12645     p = q;
12646     if (*p == '=') {
12647       p++;
12648       if (*p == '\"') {
12649         p++;
12650         while (*p && *p != '\"') p++;
12651         if (*p == '\"') p++;
12652       } else {
12653         while (*p && *p != ' ') p++;
12654       }
12655     }
12656   }
12657
12658 }
12659
12660 void
12661 PeriodicUpdatesEvent(newState)
12662      int newState;
12663 {
12664     if (newState == appData.periodicUpdates)
12665       return;
12666
12667     appData.periodicUpdates=newState;
12668
12669     /* Display type changes, so update it now */
12670     DisplayAnalysis();
12671
12672     /* Get the ball rolling again... */
12673     if (newState) {
12674         AnalysisPeriodicEvent(1);
12675         StartAnalysisClock();
12676     }
12677 }
12678
12679 void
12680 PonderNextMoveEvent(newState)
12681      int newState;
12682 {
12683     if (newState == appData.ponderNextMove) return;
12684     if (gameMode == EditPosition) EditPositionDone();
12685     if (newState) {
12686         SendToProgram("hard\n", &first);
12687         if (gameMode == TwoMachinesPlay) {
12688             SendToProgram("hard\n", &second);
12689         }
12690     } else {
12691         SendToProgram("easy\n", &first);
12692         thinkOutput[0] = NULLCHAR;
12693         if (gameMode == TwoMachinesPlay) {
12694             SendToProgram("easy\n", &second);
12695         }
12696     }
12697     appData.ponderNextMove = newState;
12698 }
12699
12700 void
12701 NewSettingEvent(option, command, value)
12702      char *command;
12703      int option, value;
12704 {
12705     char buf[MSG_SIZ];
12706
12707     if (gameMode == EditPosition) EditPositionDone();
12708     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12709     SendToProgram(buf, &first);
12710     if (gameMode == TwoMachinesPlay) {
12711         SendToProgram(buf, &second);
12712     }
12713 }
12714
12715 void
12716 ShowThinkingEvent()
12717 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12718 {
12719     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12720     int newState = appData.showThinking
12721         // [HGM] thinking: other features now need thinking output as well
12722         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12723     
12724     if (oldState == newState) return;
12725     oldState = newState;
12726     if (gameMode == EditPosition) EditPositionDone();
12727     if (oldState) {
12728         SendToProgram("post\n", &first);
12729         if (gameMode == TwoMachinesPlay) {
12730             SendToProgram("post\n", &second);
12731         }
12732     } else {
12733         SendToProgram("nopost\n", &first);
12734         thinkOutput[0] = NULLCHAR;
12735         if (gameMode == TwoMachinesPlay) {
12736             SendToProgram("nopost\n", &second);
12737         }
12738     }
12739 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12740 }
12741
12742 void
12743 AskQuestionEvent(title, question, replyPrefix, which)
12744      char *title; char *question; char *replyPrefix; char *which;
12745 {
12746   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12747   if (pr == NoProc) return;
12748   AskQuestion(title, question, replyPrefix, pr);
12749 }
12750
12751 void
12752 DisplayMove(moveNumber)
12753      int moveNumber;
12754 {
12755     char message[MSG_SIZ];
12756     char res[MSG_SIZ];
12757     char cpThinkOutput[MSG_SIZ];
12758
12759     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12760     
12761     if (moveNumber == forwardMostMove - 1 || 
12762         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12763
12764         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12765
12766         if (strchr(cpThinkOutput, '\n')) {
12767             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12768         }
12769     } else {
12770         *cpThinkOutput = NULLCHAR;
12771     }
12772
12773     /* [AS] Hide thinking from human user */
12774     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12775         *cpThinkOutput = NULLCHAR;
12776         if( thinkOutput[0] != NULLCHAR ) {
12777             int i;
12778
12779             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12780                 cpThinkOutput[i] = '.';
12781             }
12782             cpThinkOutput[i] = NULLCHAR;
12783             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12784         }
12785     }
12786
12787     if (moveNumber == forwardMostMove - 1 &&
12788         gameInfo.resultDetails != NULL) {
12789         if (gameInfo.resultDetails[0] == NULLCHAR) {
12790             sprintf(res, " %s", PGNResult(gameInfo.result));
12791         } else {
12792             sprintf(res, " {%s} %s",
12793                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12794         }
12795     } else {
12796         res[0] = NULLCHAR;
12797     }
12798
12799     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12800         DisplayMessage(res, cpThinkOutput);
12801     } else {
12802         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12803                 WhiteOnMove(moveNumber) ? " " : ".. ",
12804                 parseList[moveNumber], res);
12805         DisplayMessage(message, cpThinkOutput);
12806     }
12807 }
12808
12809 void
12810 DisplayAnalysisText(text)
12811      char *text;
12812 {
12813     char buf[MSG_SIZ];
12814
12815     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile 
12816                || appData.icsEngineAnalyze) {
12817         sprintf(buf, "Analysis (%s)", first.tidy);
12818         AnalysisPopUp(buf, text);
12819     }
12820 }
12821
12822 static int
12823 only_one_move(str)
12824      char *str;
12825 {
12826     while (*str && isspace(*str)) ++str;
12827     while (*str && !isspace(*str)) ++str;
12828     if (!*str) return 1;
12829     while (*str && isspace(*str)) ++str;
12830     if (!*str) return 1;
12831     return 0;
12832 }
12833
12834 void
12835 DisplayAnalysis()
12836 {
12837     char buf[MSG_SIZ];
12838     char lst[MSG_SIZ / 2];
12839     double nps;
12840     static char *xtra[] = { "", " (--)", " (++)" };
12841     int h, m, s, cs;
12842   
12843     if (programStats.time == 0) {
12844         programStats.time = 1;
12845     }
12846   
12847     if (programStats.got_only_move) {
12848         safeStrCpy(buf, programStats.movelist, sizeof(buf));
12849     } else {
12850         safeStrCpy( lst, programStats.movelist, sizeof(lst));
12851
12852         nps = (u64ToDouble(programStats.nodes) /
12853              ((double)programStats.time /100.0));
12854
12855         cs = programStats.time % 100;
12856         s = programStats.time / 100;
12857         h = (s / (60*60));
12858         s = s - h*60*60;
12859         m = (s/60);
12860         s = s - m*60;
12861
12862         if (programStats.moves_left > 0 && appData.periodicUpdates) {
12863           if (programStats.move_name[0] != NULLCHAR) {
12864             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12865                     programStats.depth,
12866                     programStats.nr_moves-programStats.moves_left,
12867                     programStats.nr_moves, programStats.move_name,
12868                     ((float)programStats.score)/100.0, lst,
12869                     only_one_move(lst)?
12870                     xtra[programStats.got_fail] : "",
12871                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12872           } else {
12873             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12874                     programStats.depth,
12875                     programStats.nr_moves-programStats.moves_left,
12876                     programStats.nr_moves, ((float)programStats.score)/100.0,
12877                     lst,
12878                     only_one_move(lst)?
12879                     xtra[programStats.got_fail] : "",
12880                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12881           }
12882         } else {
12883             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12884                     programStats.depth,
12885                     ((float)programStats.score)/100.0,
12886                     lst,
12887                     only_one_move(lst)?
12888                     xtra[programStats.got_fail] : "",
12889                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12890         }
12891     }
12892     DisplayAnalysisText(buf);
12893 }
12894
12895 void
12896 DisplayComment(moveNumber, text)
12897      int moveNumber;
12898      char *text;
12899 {
12900     char title[MSG_SIZ];
12901     char buf[8000]; // comment can be long!
12902     int score, depth;
12903
12904     if( appData.autoDisplayComment ) {
12905         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12906             strcpy(title, "Comment");
12907         } else {
12908             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12909                     WhiteOnMove(moveNumber) ? " " : ".. ",
12910                     parseList[moveNumber]);
12911         }
12912         // [HGM] PV info: display PV info together with (or as) comment
12913         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12914             if(text == NULL) text = "";                                           
12915             score = pvInfoList[moveNumber].score;
12916             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12917                               depth, (pvInfoList[moveNumber].time+50)/100, text);
12918             text = buf;
12919         }
12920     } else title[0] = 0;
12921
12922     if (text != NULL)
12923         CommentPopUp(title, text);
12924 }
12925
12926 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12927  * might be busy thinking or pondering.  It can be omitted if your
12928  * gnuchess is configured to stop thinking immediately on any user
12929  * input.  However, that gnuchess feature depends on the FIONREAD
12930  * ioctl, which does not work properly on some flavors of Unix.
12931  */
12932 void
12933 Attention(cps)
12934      ChessProgramState *cps;
12935 {
12936 #if ATTENTION
12937     if (!cps->useSigint) return;
12938     if (appData.noChessProgram || (cps->pr == NoProc)) return;
12939     switch (gameMode) {
12940       case MachinePlaysWhite:
12941       case MachinePlaysBlack:
12942       case TwoMachinesPlay:
12943       case IcsPlayingWhite:
12944       case IcsPlayingBlack:
12945       case AnalyzeMode:
12946       case AnalyzeFile:
12947         /* Skip if we know it isn't thinking */
12948         if (!cps->maybeThinking) return;
12949         if (appData.debugMode)
12950           fprintf(debugFP, "Interrupting %s\n", cps->which);
12951         InterruptChildProcess(cps->pr);
12952         cps->maybeThinking = FALSE;
12953         break;
12954       default:
12955         break;
12956     }
12957 #endif /*ATTENTION*/
12958 }
12959
12960 int
12961 CheckFlags()
12962 {
12963     if (whiteTimeRemaining <= 0) {
12964         if (!whiteFlag) {
12965             whiteFlag = TRUE;
12966             if (appData.icsActive) {
12967                 if (appData.autoCallFlag &&
12968                     gameMode == IcsPlayingBlack && !blackFlag) {
12969                   SendToICS(ics_prefix);
12970                   SendToICS("flag\n");
12971                 }
12972             } else {
12973                 if (blackFlag) {
12974                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12975                 } else {
12976                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12977                     if (appData.autoCallFlag) {
12978                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12979                         return TRUE;
12980                     }
12981                 }
12982             }
12983         }
12984     }
12985     if (blackTimeRemaining <= 0) {
12986         if (!blackFlag) {
12987             blackFlag = TRUE;
12988             if (appData.icsActive) {
12989                 if (appData.autoCallFlag &&
12990                     gameMode == IcsPlayingWhite && !whiteFlag) {
12991                   SendToICS(ics_prefix);
12992                   SendToICS("flag\n");
12993                 }
12994             } else {
12995                 if (whiteFlag) {
12996                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12997                 } else {
12998                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12999                     if (appData.autoCallFlag) {
13000                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13001                         return TRUE;
13002                     }
13003                 }
13004             }
13005         }
13006     }
13007     return FALSE;
13008 }
13009
13010 void
13011 CheckTimeControl()
13012 {
13013     if (!appData.clockMode || appData.icsActive ||
13014         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13015
13016     /*
13017      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13018      */
13019     if ( !WhiteOnMove(forwardMostMove) )
13020         /* White made time control */
13021         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13022         /* [HGM] time odds: correct new time quota for time odds! */
13023                                             / WhitePlayer()->timeOdds;
13024       else
13025         /* Black made time control */
13026         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13027                                             / WhitePlayer()->other->timeOdds;
13028 }
13029
13030 void
13031 DisplayBothClocks()
13032 {
13033     int wom = gameMode == EditPosition ?
13034       !blackPlaysFirst : WhiteOnMove(currentMove);
13035     DisplayWhiteClock(whiteTimeRemaining, wom);
13036     DisplayBlackClock(blackTimeRemaining, !wom);
13037 }
13038
13039
13040 /* Timekeeping seems to be a portability nightmare.  I think everyone
13041    has ftime(), but I'm really not sure, so I'm including some ifdefs
13042    to use other calls if you don't.  Clocks will be less accurate if
13043    you have neither ftime nor gettimeofday.
13044 */
13045
13046 /* VS 2008 requires the #include outside of the function */
13047 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13048 #include <sys/timeb.h>
13049 #endif
13050
13051 /* Get the current time as a TimeMark */
13052 void
13053 GetTimeMark(tm)
13054      TimeMark *tm;
13055 {
13056 #if HAVE_GETTIMEOFDAY
13057
13058     struct timeval timeVal;
13059     struct timezone timeZone;
13060
13061     gettimeofday(&timeVal, &timeZone);
13062     tm->sec = (long) timeVal.tv_sec; 
13063     tm->ms = (int) (timeVal.tv_usec / 1000L);
13064
13065 #else /*!HAVE_GETTIMEOFDAY*/
13066 #if HAVE_FTIME
13067
13068 // include <sys/timeb.h> / moved to just above start of function
13069     struct timeb timeB;
13070
13071     ftime(&timeB);
13072     tm->sec = (long) timeB.time;
13073     tm->ms = (int) timeB.millitm;
13074
13075 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13076     tm->sec = (long) time(NULL);
13077     tm->ms = 0;
13078 #endif
13079 #endif
13080 }
13081
13082 /* Return the difference in milliseconds between two
13083    time marks.  We assume the difference will fit in a long!
13084 */
13085 long
13086 SubtractTimeMarks(tm2, tm1)
13087      TimeMark *tm2, *tm1;
13088 {
13089     return 1000L*(tm2->sec - tm1->sec) +
13090            (long) (tm2->ms - tm1->ms);
13091 }
13092
13093
13094 /*
13095  * Code to manage the game clocks.
13096  *
13097  * In tournament play, black starts the clock and then white makes a move.
13098  * We give the human user a slight advantage if he is playing white---the
13099  * clocks don't run until he makes his first move, so it takes zero time.
13100  * Also, we don't account for network lag, so we could get out of sync
13101  * with GNU Chess's clock -- but then, referees are always right.  
13102  */
13103
13104 static TimeMark tickStartTM;
13105 static long intendedTickLength;
13106
13107 long
13108 NextTickLength(timeRemaining)
13109      long timeRemaining;
13110 {
13111     long nominalTickLength, nextTickLength;
13112
13113     if (timeRemaining > 0L && timeRemaining <= 10000L)
13114       nominalTickLength = 100L;
13115     else
13116       nominalTickLength = 1000L;
13117     nextTickLength = timeRemaining % nominalTickLength;
13118     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13119
13120     return nextTickLength;
13121 }
13122
13123 /* Adjust clock one minute up or down */
13124 void
13125 AdjustClock(Boolean which, int dir)
13126 {
13127     if(which) blackTimeRemaining += 60000*dir;
13128     else      whiteTimeRemaining += 60000*dir;
13129     DisplayBothClocks();
13130 }
13131
13132 /* Stop clocks and reset to a fresh time control */
13133 void
13134 ResetClocks() 
13135 {
13136     (void) StopClockTimer();
13137     if (appData.icsActive) {
13138         whiteTimeRemaining = blackTimeRemaining = 0;
13139     } else { /* [HGM] correct new time quote for time odds */
13140         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13141         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13142     }
13143     if (whiteFlag || blackFlag) {
13144         DisplayTitle("");
13145         whiteFlag = blackFlag = FALSE;
13146     }
13147     DisplayBothClocks();
13148 }
13149
13150 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13151
13152 /* Decrement running clock by amount of time that has passed */
13153 void
13154 DecrementClocks()
13155 {
13156     long timeRemaining;
13157     long lastTickLength, fudge;
13158     TimeMark now;
13159
13160     if (!appData.clockMode) return;
13161     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13162         
13163     GetTimeMark(&now);
13164
13165     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13166
13167     /* Fudge if we woke up a little too soon */
13168     fudge = intendedTickLength - lastTickLength;
13169     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13170
13171     if (WhiteOnMove(forwardMostMove)) {
13172         if(whiteNPS >= 0) lastTickLength = 0;
13173         timeRemaining = whiteTimeRemaining -= lastTickLength;
13174         DisplayWhiteClock(whiteTimeRemaining - fudge,
13175                           WhiteOnMove(currentMove));
13176     } else {
13177         if(blackNPS >= 0) lastTickLength = 0;
13178         timeRemaining = blackTimeRemaining -= lastTickLength;
13179         DisplayBlackClock(blackTimeRemaining - fudge,
13180                           !WhiteOnMove(currentMove));
13181     }
13182
13183     if (CheckFlags()) return;
13184         
13185     tickStartTM = now;
13186     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13187     StartClockTimer(intendedTickLength);
13188
13189     /* if the time remaining has fallen below the alarm threshold, sound the
13190      * alarm. if the alarm has sounded and (due to a takeback or time control
13191      * with increment) the time remaining has increased to a level above the
13192      * threshold, reset the alarm so it can sound again. 
13193      */
13194     
13195     if (appData.icsActive && appData.icsAlarm) {
13196
13197         /* make sure we are dealing with the user's clock */
13198         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13199                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13200            )) return;
13201
13202         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13203             alarmSounded = FALSE;
13204         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13205             PlayAlarmSound();
13206             alarmSounded = TRUE;
13207         }
13208     }
13209 }
13210
13211
13212 /* A player has just moved, so stop the previously running
13213    clock and (if in clock mode) start the other one.
13214    We redisplay both clocks in case we're in ICS mode, because
13215    ICS gives us an update to both clocks after every move.
13216    Note that this routine is called *after* forwardMostMove
13217    is updated, so the last fractional tick must be subtracted
13218    from the color that is *not* on move now.
13219 */
13220 void
13221 SwitchClocks()
13222 {
13223     long lastTickLength;
13224     TimeMark now;
13225     int flagged = FALSE;
13226
13227     GetTimeMark(&now);
13228
13229     if (StopClockTimer() && appData.clockMode) {
13230         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13231         if (WhiteOnMove(forwardMostMove)) {
13232             if(blackNPS >= 0) lastTickLength = 0;
13233             blackTimeRemaining -= lastTickLength;
13234            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13235 //         if(pvInfoList[forwardMostMove-1].time == -1)
13236                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13237                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13238         } else {
13239            if(whiteNPS >= 0) lastTickLength = 0;
13240            whiteTimeRemaining -= lastTickLength;
13241            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13242 //         if(pvInfoList[forwardMostMove-1].time == -1)
13243                  pvInfoList[forwardMostMove-1].time = 
13244                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13245         }
13246         flagged = CheckFlags();
13247     }
13248     CheckTimeControl();
13249
13250     if (flagged || !appData.clockMode) return;
13251
13252     switch (gameMode) {
13253       case MachinePlaysBlack:
13254       case MachinePlaysWhite:
13255       case BeginningOfGame:
13256         if (pausing) return;
13257         break;
13258
13259       case EditGame:
13260       case PlayFromGameFile:
13261       case IcsExamining:
13262         return;
13263
13264       default:
13265         break;
13266     }
13267
13268     tickStartTM = now;
13269     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13270       whiteTimeRemaining : blackTimeRemaining);
13271     StartClockTimer(intendedTickLength);
13272 }
13273         
13274
13275 /* Stop both clocks */
13276 void
13277 StopClocks()
13278 {       
13279     long lastTickLength;
13280     TimeMark now;
13281
13282     if (!StopClockTimer()) return;
13283     if (!appData.clockMode) return;
13284
13285     GetTimeMark(&now);
13286
13287     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13288     if (WhiteOnMove(forwardMostMove)) {
13289         if(whiteNPS >= 0) lastTickLength = 0;
13290         whiteTimeRemaining -= lastTickLength;
13291         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13292     } else {
13293         if(blackNPS >= 0) lastTickLength = 0;
13294         blackTimeRemaining -= lastTickLength;
13295         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13296     }
13297     CheckFlags();
13298 }
13299         
13300 /* Start clock of player on move.  Time may have been reset, so
13301    if clock is already running, stop and restart it. */
13302 void
13303 StartClocks()
13304 {
13305     (void) StopClockTimer(); /* in case it was running already */
13306     DisplayBothClocks();
13307     if (CheckFlags()) return;
13308
13309     if (!appData.clockMode) return;
13310     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13311
13312     GetTimeMark(&tickStartTM);
13313     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13314       whiteTimeRemaining : blackTimeRemaining);
13315
13316    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13317     whiteNPS = blackNPS = -1; 
13318     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13319        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13320         whiteNPS = first.nps;
13321     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13322        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13323         blackNPS = first.nps;
13324     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13325         whiteNPS = second.nps;
13326     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13327         blackNPS = second.nps;
13328     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13329
13330     StartClockTimer(intendedTickLength);
13331 }
13332
13333 char *
13334 TimeString(ms)
13335      long ms;
13336 {
13337     long second, minute, hour, day;
13338     char *sign = "";
13339     static char buf[32];
13340     
13341     if (ms > 0 && ms <= 9900) {
13342       /* convert milliseconds to tenths, rounding up */
13343       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13344
13345       sprintf(buf, " %03.1f ", tenths/10.0);
13346       return buf;
13347     }
13348
13349     /* convert milliseconds to seconds, rounding up */
13350     /* use floating point to avoid strangeness of integer division
13351        with negative dividends on many machines */
13352     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13353
13354     if (second < 0) {
13355         sign = "-";
13356         second = -second;
13357     }
13358     
13359     day = second / (60 * 60 * 24);
13360     second = second % (60 * 60 * 24);
13361     hour = second / (60 * 60);
13362     second = second % (60 * 60);
13363     minute = second / 60;
13364     second = second % 60;
13365     
13366     if (day > 0)
13367       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13368               sign, day, hour, minute, second);
13369     else if (hour > 0)
13370       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13371     else
13372       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13373     
13374     return buf;
13375 }
13376
13377
13378 /*
13379  * This is necessary because some C libraries aren't ANSI C compliant yet.
13380  */
13381 char *
13382 StrStr(string, match)
13383      char *string, *match;
13384 {
13385     int i, length;
13386     
13387     length = strlen(match);
13388     
13389     for (i = strlen(string) - length; i >= 0; i--, string++)
13390       if (!strncmp(match, string, length))
13391         return string;
13392     
13393     return NULL;
13394 }
13395
13396 char *
13397 StrCaseStr(string, match)
13398      char *string, *match;
13399 {
13400     int i, j, length;
13401     
13402     length = strlen(match);
13403     
13404     for (i = strlen(string) - length; i >= 0; i--, string++) {
13405         for (j = 0; j < length; j++) {
13406             if (ToLower(match[j]) != ToLower(string[j]))
13407               break;
13408         }
13409         if (j == length) return string;
13410     }
13411
13412     return NULL;
13413 }
13414
13415 #ifndef _amigados
13416 int
13417 StrCaseCmp(s1, s2)
13418      char *s1, *s2;
13419 {
13420     char c1, c2;
13421     
13422     for (;;) {
13423         c1 = ToLower(*s1++);
13424         c2 = ToLower(*s2++);
13425         if (c1 > c2) return 1;
13426         if (c1 < c2) return -1;
13427         if (c1 == NULLCHAR) return 0;
13428     }
13429 }
13430
13431
13432 int
13433 ToLower(c)
13434      int c;
13435 {
13436     return isupper(c) ? tolower(c) : c;
13437 }
13438
13439
13440 int
13441 ToUpper(c)
13442      int c;
13443 {
13444     return islower(c) ? toupper(c) : c;
13445 }
13446 #endif /* !_amigados    */
13447
13448 char *
13449 StrSave(s)
13450      char *s;
13451 {
13452     char *ret;
13453
13454     if ((ret = (char *) malloc(strlen(s) + 1))) {
13455         strcpy(ret, s);
13456     }
13457     return ret;
13458 }
13459
13460 char *
13461 StrSavePtr(s, savePtr)
13462      char *s, **savePtr;
13463 {
13464     if (*savePtr) {
13465         free(*savePtr);
13466     }
13467     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13468         strcpy(*savePtr, s);
13469     }
13470     return(*savePtr);
13471 }
13472
13473 char *
13474 PGNDate()
13475 {
13476     time_t clock;
13477     struct tm *tm;
13478     char buf[MSG_SIZ];
13479
13480     clock = time((time_t *)NULL);
13481     tm = localtime(&clock);
13482     sprintf(buf, "%04d.%02d.%02d",
13483             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13484     return StrSave(buf);
13485 }
13486
13487
13488 char *
13489 PositionToFEN(move, overrideCastling)
13490      int move;
13491      char *overrideCastling;
13492 {
13493     int i, j, fromX, fromY, toX, toY;
13494     int whiteToPlay;
13495     char buf[128];
13496     char *p, *q;
13497     int emptycount;
13498     ChessSquare piece;
13499
13500     whiteToPlay = (gameMode == EditPosition) ?
13501       !blackPlaysFirst : (move % 2 == 0);
13502     p = buf;
13503
13504     /* Piece placement data */
13505     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13506         emptycount = 0;
13507         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13508             if (boards[move][i][j] == EmptySquare) {
13509                 emptycount++;
13510             } else { ChessSquare piece = boards[move][i][j];
13511                 if (emptycount > 0) {
13512                     if(emptycount<10) /* [HGM] can be >= 10 */
13513                         *p++ = '0' + emptycount;
13514                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13515                     emptycount = 0;
13516                 }
13517                 if(PieceToChar(piece) == '+') {
13518                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13519                     *p++ = '+';
13520                     piece = (ChessSquare)(DEMOTED piece);
13521                 } 
13522                 *p++ = PieceToChar(piece);
13523                 if(p[-1] == '~') {
13524                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13525                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13526                     *p++ = '~';
13527                 }
13528             }
13529         }
13530         if (emptycount > 0) {
13531             if(emptycount<10) /* [HGM] can be >= 10 */
13532                 *p++ = '0' + emptycount;
13533             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13534             emptycount = 0;
13535         }
13536         *p++ = '/';
13537     }
13538     *(p - 1) = ' ';
13539
13540     /* [HGM] print Crazyhouse or Shogi holdings */
13541     if( gameInfo.holdingsWidth ) {
13542         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13543         q = p;
13544         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13545             piece = boards[move][i][BOARD_WIDTH-1];
13546             if( piece != EmptySquare )
13547               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13548                   *p++ = PieceToChar(piece);
13549         }
13550         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13551             piece = boards[move][BOARD_HEIGHT-i-1][0];
13552             if( piece != EmptySquare )
13553               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13554                   *p++ = PieceToChar(piece);
13555         }
13556
13557         if( q == p ) *p++ = '-';
13558         *p++ = ']';
13559         *p++ = ' ';
13560     }
13561
13562     /* Active color */
13563     *p++ = whiteToPlay ? 'w' : 'b';
13564     *p++ = ' ';
13565
13566   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13567     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13568   } else {
13569   if(nrCastlingRights) {
13570      q = p;
13571      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13572        /* [HGM] write directly from rights */
13573            if(castlingRights[move][2] >= 0 &&
13574               castlingRights[move][0] >= 0   )
13575                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13576            if(castlingRights[move][2] >= 0 &&
13577               castlingRights[move][1] >= 0   )
13578                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13579            if(castlingRights[move][5] >= 0 &&
13580               castlingRights[move][3] >= 0   )
13581                 *p++ = castlingRights[move][3] + AAA;
13582            if(castlingRights[move][5] >= 0 &&
13583               castlingRights[move][4] >= 0   )
13584                 *p++ = castlingRights[move][4] + AAA;
13585      } else {
13586
13587         /* [HGM] write true castling rights */
13588         if( nrCastlingRights == 6 ) {
13589             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13590                castlingRights[move][2] >= 0  ) *p++ = 'K';
13591             if(castlingRights[move][1] == BOARD_LEFT &&
13592                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13593             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13594                castlingRights[move][5] >= 0  ) *p++ = 'k';
13595             if(castlingRights[move][4] == BOARD_LEFT &&
13596                castlingRights[move][5] >= 0  ) *p++ = 'q';
13597         }
13598      }
13599      if (q == p) *p++ = '-'; /* No castling rights */
13600      *p++ = ' ';
13601   }
13602
13603   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13604      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13605     /* En passant target square */
13606     if (move > backwardMostMove) {
13607         fromX = moveList[move - 1][0] - AAA;
13608         fromY = moveList[move - 1][1] - ONE;
13609         toX = moveList[move - 1][2] - AAA;
13610         toY = moveList[move - 1][3] - ONE;
13611         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13612             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13613             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13614             fromX == toX) {
13615             /* 2-square pawn move just happened */
13616             *p++ = toX + AAA;
13617             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13618         } else {
13619             *p++ = '-';
13620         }
13621     } else {
13622         *p++ = '-';
13623     }
13624     *p++ = ' ';
13625   }
13626   }
13627
13628     /* [HGM] find reversible plies */
13629     {   int i = 0, j=move;
13630
13631         if (appData.debugMode) { int k;
13632             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13633             for(k=backwardMostMove; k<=forwardMostMove; k++)
13634                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13635
13636         }
13637
13638         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13639         if( j == backwardMostMove ) i += initialRulePlies;
13640         sprintf(p, "%d ", i);
13641         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13642     }
13643     /* Fullmove number */
13644     sprintf(p, "%d", (move / 2) + 1);
13645     
13646     return StrSave(buf);
13647 }
13648
13649 Boolean
13650 ParseFEN(board, blackPlaysFirst, fen)
13651     Board board;
13652      int *blackPlaysFirst;
13653      char *fen;
13654 {
13655     int i, j;
13656     char *p;
13657     int emptycount;
13658     ChessSquare piece;
13659
13660     p = fen;
13661
13662     /* [HGM] by default clear Crazyhouse holdings, if present */
13663     if(gameInfo.holdingsWidth) {
13664        for(i=0; i<BOARD_HEIGHT; i++) {
13665            board[i][0]             = EmptySquare; /* black holdings */
13666            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13667            board[i][1]             = (ChessSquare) 0; /* black counts */
13668            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13669        }
13670     }
13671
13672     /* Piece placement data */
13673     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13674         j = 0;
13675         for (;;) {
13676             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13677                 if (*p == '/') p++;
13678                 emptycount = gameInfo.boardWidth - j;
13679                 while (emptycount--)
13680                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13681                 break;
13682 #if(BOARD_SIZE >= 10)
13683             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13684                 p++; emptycount=10;
13685                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13686                 while (emptycount--)
13687                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13688 #endif
13689             } else if (isdigit(*p)) {
13690                 emptycount = *p++ - '0';
13691                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13692                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13693                 while (emptycount--)
13694                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13695             } else if (*p == '+' || isalpha(*p)) {
13696                 if (j >= gameInfo.boardWidth) return FALSE;
13697                 if(*p=='+') {
13698                     piece = CharToPiece(*++p);
13699                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13700                     piece = (ChessSquare) (PROMOTED piece ); p++;
13701                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13702                 } else piece = CharToPiece(*p++);
13703
13704                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13705                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13706                     piece = (ChessSquare) (PROMOTED piece);
13707                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13708                     p++;
13709                 }
13710                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13711             } else {
13712                 return FALSE;
13713             }
13714         }
13715     }
13716     while (*p == '/' || *p == ' ') p++;
13717
13718     /* [HGM] look for Crazyhouse holdings here */
13719     while(*p==' ') p++;
13720     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13721         if(*p == '[') p++;
13722         if(*p == '-' ) *p++; /* empty holdings */ else {
13723             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13724             /* if we would allow FEN reading to set board size, we would   */
13725             /* have to add holdings and shift the board read so far here   */
13726             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13727                 *p++;
13728                 if((int) piece >= (int) BlackPawn ) {
13729                     i = (int)piece - (int)BlackPawn;
13730                     i = PieceToNumber((ChessSquare)i);
13731                     if( i >= gameInfo.holdingsSize ) return FALSE;
13732                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13733                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13734                 } else {
13735                     i = (int)piece - (int)WhitePawn;
13736                     i = PieceToNumber((ChessSquare)i);
13737                     if( i >= gameInfo.holdingsSize ) return FALSE;
13738                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13739                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13740                 }
13741             }
13742         }
13743         if(*p == ']') *p++;
13744     }
13745
13746     while(*p == ' ') p++;
13747
13748     /* Active color */
13749     switch (*p++) {
13750       case 'w':
13751         *blackPlaysFirst = FALSE;
13752         break;
13753       case 'b': 
13754         *blackPlaysFirst = TRUE;
13755         break;
13756       default:
13757         return FALSE;
13758     }
13759
13760     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13761     /* return the extra info in global variiables             */
13762
13763     /* set defaults in case FEN is incomplete */
13764     FENepStatus = EP_UNKNOWN;
13765     for(i=0; i<nrCastlingRights; i++ ) {
13766         FENcastlingRights[i] =
13767             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13768     }   /* assume possible unless obviously impossible */
13769     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13770     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13771     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13772     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13773     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13774     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13775     FENrulePlies = 0;
13776
13777     while(*p==' ') p++;
13778     if(nrCastlingRights) {
13779       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13780           /* castling indicator present, so default becomes no castlings */
13781           for(i=0; i<nrCastlingRights; i++ ) {
13782                  FENcastlingRights[i] = -1;
13783           }
13784       }
13785       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13786              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13787              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13788              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13789         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13790
13791         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13792             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13793             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13794         }
13795         switch(c) {
13796           case'K':
13797               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13798               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13799               FENcastlingRights[2] = whiteKingFile;
13800               break;
13801           case'Q':
13802               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13803               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13804               FENcastlingRights[2] = whiteKingFile;
13805               break;
13806           case'k':
13807               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13808               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13809               FENcastlingRights[5] = blackKingFile;
13810               break;
13811           case'q':
13812               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13813               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13814               FENcastlingRights[5] = blackKingFile;
13815           case '-':
13816               break;
13817           default: /* FRC castlings */
13818               if(c >= 'a') { /* black rights */
13819                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13820                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13821                   if(i == BOARD_RGHT) break;
13822                   FENcastlingRights[5] = i;
13823                   c -= AAA;
13824                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13825                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13826                   if(c > i)
13827                       FENcastlingRights[3] = c;
13828                   else
13829                       FENcastlingRights[4] = c;
13830               } else { /* white rights */
13831                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13832                     if(board[0][i] == WhiteKing) break;
13833                   if(i == BOARD_RGHT) break;
13834                   FENcastlingRights[2] = i;
13835                   c -= AAA - 'a' + 'A';
13836                   if(board[0][c] >= WhiteKing) break;
13837                   if(c > i)
13838                       FENcastlingRights[0] = c;
13839                   else
13840                       FENcastlingRights[1] = c;
13841               }
13842         }
13843       }
13844     if (appData.debugMode) {
13845         fprintf(debugFP, "FEN castling rights:");
13846         for(i=0; i<nrCastlingRights; i++)
13847         fprintf(debugFP, " %d", FENcastlingRights[i]);
13848         fprintf(debugFP, "\n");
13849     }
13850
13851       while(*p==' ') p++;
13852     }
13853
13854     /* read e.p. field in games that know e.p. capture */
13855     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13856        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13857       if(*p=='-') {
13858         p++; FENepStatus = EP_NONE;
13859       } else {
13860          char c = *p++ - AAA;
13861
13862          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13863          if(*p >= '0' && *p <='9') *p++;
13864          FENepStatus = c;
13865       }
13866     }
13867
13868
13869     if(sscanf(p, "%d", &i) == 1) {
13870         FENrulePlies = i; /* 50-move ply counter */
13871         /* (The move number is still ignored)    */
13872     }
13873
13874     return TRUE;
13875 }
13876       
13877 void
13878 EditPositionPasteFEN(char *fen)
13879 {
13880   if (fen != NULL) {
13881     Board initial_position;
13882
13883     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13884       DisplayError(_("Bad FEN position in clipboard"), 0);
13885       return ;
13886     } else {
13887       int savedBlackPlaysFirst = blackPlaysFirst;
13888       EditPositionEvent();
13889       blackPlaysFirst = savedBlackPlaysFirst;
13890       CopyBoard(boards[0], initial_position);
13891           /* [HGM] copy FEN attributes as well */
13892           {   int i;
13893               initialRulePlies = FENrulePlies;
13894               epStatus[0] = FENepStatus;
13895               for( i=0; i<nrCastlingRights; i++ )
13896                   castlingRights[0][i] = FENcastlingRights[i];
13897           }
13898       EditPositionDone();
13899       DisplayBothClocks();
13900       DrawPosition(FALSE, boards[currentMove]);
13901     }
13902   }
13903 }