added some comments and formated code
[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     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4561
4562     /* [AS] Initialize pv info list [HGM] and game status */
4563     {
4564         for( i=0; i<MAX_MOVES; i++ ) {
4565             pvInfoList[i].depth = 0;
4566             epStatus[i]=EP_NONE;
4567             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4568         }
4569
4570         initialRulePlies = 0; /* 50-move counter start */
4571
4572         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4573         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4574     }
4575
4576     
4577     /* [HGM] logic here is completely changed. In stead of full positions */
4578     /* the initialized data only consist of the two backranks. The switch */
4579     /* selects which one we will use, which is than copied to the Board   */
4580     /* initialPosition, which for the rest is initialized by Pawns and    */
4581     /* empty squares. This initial position is then copied to boards[0],  */
4582     /* possibly after shuffling, so that it remains available.            */
4583
4584     gameInfo.holdingsWidth = 0; /* default board sizes */
4585     gameInfo.boardWidth    = 8;
4586     gameInfo.boardHeight   = 8;
4587     gameInfo.holdingsSize  = 0;
4588     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4589     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4590     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4591
4592     switch (gameInfo.variant) {
4593     case VariantFischeRandom:
4594       shuffleOpenings = TRUE;
4595     default:
4596       pieces = FIDEArray;
4597       break;
4598     case VariantShatranj:
4599       pieces = ShatranjArray;
4600       nrCastlingRights = 0;
4601       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4602       break;
4603     case VariantTwoKings:
4604       pieces = twoKingsArray;
4605       break;
4606     case VariantCapaRandom:
4607       shuffleOpenings = TRUE;
4608     case VariantCapablanca:
4609       pieces = CapablancaArray;
4610       gameInfo.boardWidth = 10;
4611       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4612       break;
4613     case VariantGothic:
4614       pieces = GothicArray;
4615       gameInfo.boardWidth = 10;
4616       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4617       break;
4618     case VariantJanus:
4619       pieces = JanusArray;
4620       gameInfo.boardWidth = 10;
4621       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4622       nrCastlingRights = 6;
4623         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4624         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4625         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4626         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4627         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4628         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4629       break;
4630     case VariantFalcon:
4631       pieces = FalconArray;
4632       gameInfo.boardWidth = 10;
4633       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4634       break;
4635     case VariantXiangqi:
4636       pieces = XiangqiArray;
4637       gameInfo.boardWidth  = 9;
4638       gameInfo.boardHeight = 10;
4639       nrCastlingRights = 0;
4640       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4641       break;
4642     case VariantShogi:
4643       pieces = ShogiArray;
4644       gameInfo.boardWidth  = 9;
4645       gameInfo.boardHeight = 9;
4646       gameInfo.holdingsSize = 7;
4647       nrCastlingRights = 0;
4648       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4649       break;
4650     case VariantCourier:
4651       pieces = CourierArray;
4652       gameInfo.boardWidth  = 12;
4653       nrCastlingRights = 0;
4654       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4655       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4656       break;
4657     case VariantKnightmate:
4658       pieces = KnightmateArray;
4659       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4660       break;
4661     case VariantFairy:
4662       pieces = fairyArray;
4663       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4664       break;
4665     case VariantGreat:
4666       pieces = GreatArray;
4667       gameInfo.boardWidth = 10;
4668       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4669       gameInfo.holdingsSize = 8;
4670       break;
4671     case VariantSuper:
4672       pieces = FIDEArray;
4673       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4674       gameInfo.holdingsSize = 8;
4675       startedFromSetupPosition = TRUE;
4676       break;
4677     case VariantCrazyhouse:
4678     case VariantBughouse:
4679       pieces = FIDEArray;
4680       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4681       gameInfo.holdingsSize = 5;
4682       break;
4683     case VariantWildCastle:
4684       pieces = FIDEArray;
4685       /* !!?shuffle with kings guaranteed to be on d or e file */
4686       shuffleOpenings = 1;
4687       break;
4688     case VariantNoCastle:
4689       pieces = FIDEArray;
4690       nrCastlingRights = 0;
4691       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4692       /* !!?unconstrained back-rank shuffle */
4693       shuffleOpenings = 1;
4694       break;
4695     }
4696
4697     overrule = 0;
4698     if(appData.NrFiles >= 0) {
4699         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4700         gameInfo.boardWidth = appData.NrFiles;
4701     }
4702     if(appData.NrRanks >= 0) {
4703         gameInfo.boardHeight = appData.NrRanks;
4704     }
4705     if(appData.holdingsSize >= 0) {
4706         i = appData.holdingsSize;
4707         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4708         gameInfo.holdingsSize = i;
4709     }
4710     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4711     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4712         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4713
4714     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4715     if(pawnRow < 1) pawnRow = 1;
4716
4717     /* User pieceToChar list overrules defaults */
4718     if(appData.pieceToCharTable != NULL)
4719         SetCharTable(pieceToChar, appData.pieceToCharTable);
4720
4721     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4722
4723         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4724             s = (ChessSquare) 0; /* account holding counts in guard band */
4725         for( i=0; i<BOARD_HEIGHT; i++ )
4726             initialPosition[i][j] = s;
4727
4728         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4729         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4730         initialPosition[pawnRow][j] = WhitePawn;
4731         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4732         if(gameInfo.variant == VariantXiangqi) {
4733             if(j&1) {
4734                 initialPosition[pawnRow][j] = 
4735                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4736                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4737                    initialPosition[2][j] = WhiteCannon;
4738                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4739                 }
4740             }
4741         }
4742         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4743     }
4744     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4745
4746             j=BOARD_LEFT+1;
4747             initialPosition[1][j] = WhiteBishop;
4748             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4749             j=BOARD_RGHT-2;
4750             initialPosition[1][j] = WhiteRook;
4751             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4752     }
4753
4754     if( nrCastlingRights == -1) {
4755         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4756         /*       This sets default castling rights from none to normal corners   */
4757         /* Variants with other castling rights must set them themselves above    */
4758         nrCastlingRights = 6;
4759        
4760         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4761         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4762         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4763         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4764         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4765         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4766      }
4767
4768      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4769      if(gameInfo.variant == VariantGreat) { // promotion commoners
4770         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4771         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4772         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4773         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4774      }
4775 #if 0
4776     if(gameInfo.variant == VariantFischeRandom) {
4777       if( appData.defaultFrcPosition < 0 ) {
4778         ShuffleFRC( initialPosition );
4779       }
4780       else {
4781         SetupFRC( initialPosition, appData.defaultFrcPosition );
4782       }
4783       startedFromSetupPosition = TRUE;
4784     } else 
4785 #else
4786   if (appData.debugMode) {
4787     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4788   }
4789     if(shuffleOpenings) {
4790         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4791         startedFromSetupPosition = TRUE;
4792     }
4793 #endif
4794     if(startedFromPositionFile) {
4795       /* [HGM] loadPos: use PositionFile for every new game */
4796       CopyBoard(initialPosition, filePosition);
4797       for(i=0; i<nrCastlingRights; i++)
4798           castlingRights[0][i] = initialRights[i] = fileRights[i];
4799       startedFromSetupPosition = TRUE;
4800     }
4801
4802     CopyBoard(boards[0], initialPosition);
4803
4804     if(oldx != gameInfo.boardWidth ||
4805        oldy != gameInfo.boardHeight ||
4806        oldh != gameInfo.holdingsWidth
4807 #ifdef GOTHIC
4808        || oldv == VariantGothic ||        // For licensing popups
4809        gameInfo.variant == VariantGothic
4810 #endif
4811 #ifdef FALCON
4812        || oldv == VariantFalcon ||
4813        gameInfo.variant == VariantFalcon
4814 #endif
4815                                          )
4816             InitDrawingSizes(-2 ,0);
4817
4818     if (redraw)
4819       DrawPosition(TRUE, boards[currentMove]);
4820 }
4821
4822 void
4823 SendBoard(cps, moveNum)
4824      ChessProgramState *cps;
4825      int moveNum;
4826 {
4827     char message[MSG_SIZ];
4828     
4829     if (cps->useSetboard) {
4830       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4831       sprintf(message, "setboard %s\n", fen);
4832       SendToProgram(message, cps);
4833       free(fen);
4834
4835     } else {
4836       ChessSquare *bp;
4837       int i, j;
4838       /* Kludge to set black to move, avoiding the troublesome and now
4839        * deprecated "black" command.
4840        */
4841       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4842
4843       SendToProgram("edit\n", cps);
4844       SendToProgram("#\n", cps);
4845       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4846         bp = &boards[moveNum][i][BOARD_LEFT];
4847         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4848           if ((int) *bp < (int) BlackPawn) {
4849             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4850                     AAA + j, ONE + i);
4851             if(message[0] == '+' || message[0] == '~') {
4852                 sprintf(message, "%c%c%c+\n",
4853                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4854                         AAA + j, ONE + i);
4855             }
4856             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4857                 message[1] = BOARD_RGHT   - 1 - j + '1';
4858                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4859             }
4860             SendToProgram(message, cps);
4861           }
4862         }
4863       }
4864     
4865       SendToProgram("c\n", cps);
4866       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4867         bp = &boards[moveNum][i][BOARD_LEFT];
4868         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4869           if (((int) *bp != (int) EmptySquare)
4870               && ((int) *bp >= (int) BlackPawn)) {
4871             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4872                     AAA + j, ONE + i);
4873             if(message[0] == '+' || message[0] == '~') {
4874                 sprintf(message, "%c%c%c+\n",
4875                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4876                         AAA + j, ONE + i);
4877             }
4878             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4879                 message[1] = BOARD_RGHT   - 1 - j + '1';
4880                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4881             }
4882             SendToProgram(message, cps);
4883           }
4884         }
4885       }
4886     
4887       SendToProgram(".\n", cps);
4888     }
4889     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4890 }
4891
4892 int
4893 IsPromotion(fromX, fromY, toX, toY)
4894      int fromX, fromY, toX, toY;
4895 {
4896     /* [HGM] add Shogi promotions */
4897     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4898     ChessSquare piece;
4899
4900     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4901       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4902    /* [HGM] Note to self: line above also weeds out drops */
4903     piece = boards[currentMove][fromY][fromX];
4904     if(gameInfo.variant == VariantShogi) {
4905         promotionZoneSize = 3;
4906         highestPromotingPiece = (int)WhiteKing;
4907         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4908            and if in normal chess we then allow promotion to King, why not
4909            allow promotion of other piece in Shogi?                         */
4910     }
4911     if((int)piece >= BlackPawn) {
4912         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4913              return FALSE;
4914         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4915     } else {
4916         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4917            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4918     }
4919     return ( (int)piece <= highestPromotingPiece );
4920 }
4921
4922 int
4923 InPalace(row, column)
4924      int row, column;
4925 {   /* [HGM] for Xiangqi */
4926     if( (row < 3 || row > BOARD_HEIGHT-4) &&
4927          column < (BOARD_WIDTH + 4)/2 &&
4928          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4929     return FALSE;
4930 }
4931
4932 int
4933 PieceForSquare (x, y)
4934      int x;
4935      int y;
4936 {
4937   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4938      return -1;
4939   else
4940      return boards[currentMove][y][x];
4941 }
4942
4943 int
4944 OKToStartUserMove(x, y)
4945      int x, y;
4946 {
4947     ChessSquare from_piece;
4948     int white_piece;
4949
4950     if (matchMode) return FALSE;
4951     if (gameMode == EditPosition) return TRUE;
4952
4953     if (x >= 0 && y >= 0)
4954       from_piece = boards[currentMove][y][x];
4955     else
4956       from_piece = EmptySquare;
4957
4958     if (from_piece == EmptySquare) return FALSE;
4959
4960     white_piece = (int)from_piece >= (int)WhitePawn &&
4961       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4962
4963     switch (gameMode) {
4964       case PlayFromGameFile:
4965       case AnalyzeFile:
4966       case TwoMachinesPlay:
4967       case EndOfGame:
4968         return FALSE;
4969
4970       case IcsObserving:
4971       case IcsIdle:
4972         return FALSE;
4973
4974       case MachinePlaysWhite:
4975       case IcsPlayingBlack:
4976         if (appData.zippyPlay) return FALSE;
4977         if (white_piece) {
4978             DisplayMoveError(_("You are playing Black"));
4979             return FALSE;
4980         }
4981         break;
4982
4983       case MachinePlaysBlack:
4984       case IcsPlayingWhite:
4985         if (appData.zippyPlay) return FALSE;
4986         if (!white_piece) {
4987             DisplayMoveError(_("You are playing White"));
4988             return FALSE;
4989         }
4990         break;
4991
4992       case EditGame:
4993         if (!white_piece && WhiteOnMove(currentMove)) {
4994             DisplayMoveError(_("It is White's turn"));
4995             return FALSE;
4996         }           
4997         if (white_piece && !WhiteOnMove(currentMove)) {
4998             DisplayMoveError(_("It is Black's turn"));
4999             return FALSE;
5000         }           
5001         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5002             /* Editing correspondence game history */
5003             /* Could disallow this or prompt for confirmation */
5004             cmailOldMove = -1;
5005         }
5006         if (currentMove < forwardMostMove) {
5007             /* Discarding moves */
5008             /* Could prompt for confirmation here,
5009                but I don't think that's such a good idea */
5010             forwardMostMove = currentMove;
5011         }
5012         break;
5013
5014       case BeginningOfGame:
5015         if (appData.icsActive) return FALSE;
5016         if (!appData.noChessProgram) {
5017             if (!white_piece) {
5018                 DisplayMoveError(_("You are playing White"));
5019                 return FALSE;
5020             }
5021         }
5022         break;
5023         
5024       case Training:
5025         if (!white_piece && WhiteOnMove(currentMove)) {
5026             DisplayMoveError(_("It is White's turn"));
5027             return FALSE;
5028         }           
5029         if (white_piece && !WhiteOnMove(currentMove)) {
5030             DisplayMoveError(_("It is Black's turn"));
5031             return FALSE;
5032         }           
5033         break;
5034
5035       default:
5036       case IcsExamining:
5037         break;
5038     }
5039     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5040         && gameMode != AnalyzeFile && gameMode != Training) {
5041         DisplayMoveError(_("Displayed position is not current"));
5042         return FALSE;
5043     }
5044     return TRUE;
5045 }
5046
5047 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5048 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5049 int lastLoadGameUseList = FALSE;
5050 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5051 ChessMove lastLoadGameStart = (ChessMove) 0;
5052
5053
5054 ChessMove
5055 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5056      int fromX, fromY, toX, toY;
5057      int promoChar;
5058      Boolean captureOwn;
5059 {
5060     ChessMove moveType;
5061     ChessSquare pdown, pup;
5062
5063     if (fromX < 0 || fromY < 0) return ImpossibleMove;
5064
5065     /* [HGM] suppress all moves into holdings area and guard band */
5066     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5067             return ImpossibleMove;
5068
5069     /* [HGM] <sameColor> moved to here from winboard.c */
5070     /* note: capture of own piece can be legal as drag-drop premove. For click-click it is selection of new piece. */
5071     pdown = boards[currentMove][fromY][fromX];
5072     pup = boards[currentMove][toY][toX];
5073     if (    gameMode != EditPosition && !captureOwn &&
5074             (WhitePawn <= pdown && pdown < BlackPawn &&
5075              WhitePawn <= pup && pup < BlackPawn  ||
5076              BlackPawn <= pdown && pdown < EmptySquare &&
5077              BlackPawn <= pup && pup < EmptySquare 
5078             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5079                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5080                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 ||
5081                      pup == WhiteKing && pdown == WhiteRook && fromY == 0 && toY == 0|| // also allow RxK
5082                      pup == BlackKing && pdown == BlackRook && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) 
5083         )           )
5084          return Comment;
5085
5086     /* Check if the user is playing in turn.  This is complicated because we
5087        let the user "pick up" a piece before it is his turn.  So the piece he
5088        tried to pick up may have been captured by the time he puts it down!
5089        Therefore we use the color the user is supposed to be playing in this
5090        test, not the color of the piece that is currently on the starting
5091        square---except in EditGame mode, where the user is playing both
5092        sides; fortunately there the capture race can't happen.  (It can
5093        now happen in IcsExamining mode, but that's just too bad.  The user
5094        will get a somewhat confusing message in that case.)
5095        */
5096
5097     switch (gameMode) {
5098       case PlayFromGameFile:
5099       case AnalyzeFile:
5100       case TwoMachinesPlay:
5101       case EndOfGame:
5102       case IcsObserving:
5103       case IcsIdle:
5104         /* We switched into a game mode where moves are not accepted,
5105            perhaps while the mouse button was down. */
5106         return ImpossibleMove;
5107
5108       case MachinePlaysWhite:
5109         /* User is moving for Black */
5110         if (WhiteOnMove(currentMove)) {
5111             DisplayMoveError(_("It is White's turn"));
5112             return ImpossibleMove;
5113         }
5114         break;
5115
5116       case MachinePlaysBlack:
5117         /* User is moving for White */
5118         if (!WhiteOnMove(currentMove)) {
5119             DisplayMoveError(_("It is Black's turn"));
5120             return ImpossibleMove;
5121         }
5122         break;
5123
5124       case EditGame:
5125       case IcsExamining:
5126       case BeginningOfGame:
5127       case AnalyzeMode:
5128       case Training:
5129         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5130             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5131             /* User is moving for Black */
5132             if (WhiteOnMove(currentMove)) {
5133                 DisplayMoveError(_("It is White's turn"));
5134                 return ImpossibleMove;
5135             }
5136         } else {
5137             /* User is moving for White */
5138             if (!WhiteOnMove(currentMove)) {
5139                 DisplayMoveError(_("It is Black's turn"));
5140                 return ImpossibleMove;
5141             }
5142         }
5143         break;
5144
5145       case IcsPlayingBlack:
5146         /* User is moving for Black */
5147         if (WhiteOnMove(currentMove)) {
5148             if (!appData.premove) {
5149                 DisplayMoveError(_("It is White's turn"));
5150             } else if (toX >= 0 && toY >= 0) {
5151                 premoveToX = toX;
5152                 premoveToY = toY;
5153                 premoveFromX = fromX;
5154                 premoveFromY = fromY;
5155                 premovePromoChar = promoChar;
5156                 gotPremove = 1;
5157                 if (appData.debugMode) 
5158                     fprintf(debugFP, "Got premove: fromX %d,"
5159                             "fromY %d, toX %d, toY %d\n",
5160                             fromX, fromY, toX, toY);
5161                 if(!WhiteOnMove(currentMove) && gotPremove == 1) {
5162                     // [HGM] race: we must have been hit by an opponent move from the ICS while preparing the premove
5163                     if (appData.debugMode) 
5164                         fprintf(debugFP, "Execute as normal move\n");
5165                     gotPremove = 0; break;
5166                 }
5167             }
5168             return ImpossibleMove;
5169         }
5170         break;
5171
5172       case IcsPlayingWhite:
5173         /* User is moving for White */
5174         if (!WhiteOnMove(currentMove)) {
5175             if (!appData.premove) {
5176                 DisplayMoveError(_("It is Black's turn"));
5177             } else if (toX >= 0 && toY >= 0) {
5178                 premoveToX = toX;
5179                 premoveToY = toY;
5180                 premoveFromX = fromX;
5181                 premoveFromY = fromY;
5182                 premovePromoChar = promoChar;
5183                 gotPremove = 1;
5184                 if (appData.debugMode) 
5185                     fprintf(debugFP, "Got premove: fromX %d,"
5186                             "fromY %d, toX %d, toY %d\n",
5187                             fromX, fromY, toX, toY);
5188                 if(WhiteOnMove(currentMove) && gotPremove == 1) {
5189                     // [HGM] race: we must have been hit by an opponent move from the ICS while preparing the premove
5190                     if (appData.debugMode) 
5191                         fprintf(debugFP, "Execute as normal move\n");
5192                     gotPremove = 0; break;
5193                 }
5194             }
5195             return ImpossibleMove;
5196         }
5197         break;
5198
5199       default:
5200         break;
5201
5202       case EditPosition:
5203         /* EditPosition, empty square, or different color piece;
5204            click-click move is possible */
5205         if (toX == -2 || toY == -2) {
5206             boards[0][fromY][fromX] = EmptySquare;
5207             return AmbiguousMove;
5208         } else if (toX >= 0 && toY >= 0) {
5209             boards[0][toY][toX] = boards[0][fromY][fromX];
5210             boards[0][fromY][fromX] = EmptySquare;
5211             return AmbiguousMove;
5212         }
5213         return ImpossibleMove;
5214     }
5215
5216     /* [HGM] If move started in holdings, it means a drop */
5217     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5218          if( pup != EmptySquare ) return ImpossibleMove;
5219          if(appData.testLegality) {
5220              /* it would be more logical if LegalityTest() also figured out
5221               * which drops are legal. For now we forbid pawns on back rank.
5222               * Shogi is on its own here...
5223               */
5224              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5225                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5226                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5227          }
5228          return WhiteDrop; /* Not needed to specify white or black yet */
5229     }
5230
5231     userOfferedDraw = FALSE;
5232         
5233     /* [HGM] always test for legality, to get promotion info */
5234     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5235                           epStatus[currentMove], castlingRights[currentMove],
5236                                          fromY, fromX, toY, toX, promoChar);
5237     /* [HGM] but possibly ignore an IllegalMove result */
5238     if (appData.testLegality) {
5239         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5240             DisplayMoveError(_("Illegal move"));
5241             return ImpossibleMove;
5242         }
5243     }
5244 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5245     return moveType;
5246     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5247        function is made into one that returns an OK move type if FinishMove
5248        should be called. This to give the calling driver routine the
5249        opportunity to finish the userMove input with a promotion popup,
5250        without bothering the user with this for invalid or illegal moves */
5251
5252 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5253 }
5254
5255 /* Common tail of UserMoveEvent and DropMenuEvent */
5256 int
5257 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5258      ChessMove moveType;
5259      int fromX, fromY, toX, toY;
5260      /*char*/int promoChar;
5261 {
5262     char *bookHit = 0;
5263 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5264     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5265         // [HGM] superchess: suppress promotions to non-available piece
5266         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5267         if(WhiteOnMove(currentMove)) {
5268             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5269         } else {
5270             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5271         }
5272     }
5273
5274     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5275        move type in caller when we know the move is a legal promotion */
5276     if(moveType == NormalMove && promoChar)
5277         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5278 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5279     /* [HGM] convert drag-and-drop piece drops to standard form */
5280     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5281          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5282            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5283                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5284 //         fromX = boards[currentMove][fromY][fromX];
5285            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5286            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5287            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5288            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5289          fromY = DROP_RANK;
5290     }
5291
5292     /* [HGM] <popupFix> The following if has been moved here from
5293        UserMoveEvent(). Because it seemed to belon here (why not allow
5294        piece drops in training games?), and because it can only be
5295        performed after it is known to what we promote. */
5296     if (gameMode == Training) {
5297       /* compare the move played on the board to the next move in the
5298        * game. If they match, display the move and the opponent's response. 
5299        * If they don't match, display an error message.
5300        */
5301       int saveAnimate;
5302       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5303       CopyBoard(testBoard, boards[currentMove]);
5304       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5305
5306       if (CompareBoards(testBoard, boards[currentMove+1])) {
5307         ForwardInner(currentMove+1);
5308
5309         /* Autoplay the opponent's response.
5310          * if appData.animate was TRUE when Training mode was entered,
5311          * the response will be animated.
5312          */
5313         saveAnimate = appData.animate;
5314         appData.animate = animateTraining;
5315         ForwardInner(currentMove+1);
5316         appData.animate = saveAnimate;
5317
5318         /* check for the end of the game */
5319         if (currentMove >= forwardMostMove) {
5320           gameMode = PlayFromGameFile;
5321           ModeHighlight();
5322           SetTrainingModeOff();
5323           DisplayInformation(_("End of game"));
5324         }
5325       } else {
5326         DisplayError(_("Incorrect move"), 0);
5327       }
5328       return 1;
5329     }
5330
5331   /* Ok, now we know that the move is good, so we can kill
5332      the previous line in Analysis Mode */
5333   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5334     forwardMostMove = currentMove;
5335   }
5336
5337   /* If we need the chess program but it's dead, restart it */
5338   ResurrectChessProgram();
5339
5340   /* A user move restarts a paused game*/
5341   if (pausing)
5342     PauseEvent();
5343
5344   thinkOutput[0] = NULLCHAR;
5345
5346   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5347
5348   if (gameMode == BeginningOfGame) {
5349     if (appData.noChessProgram) {
5350       gameMode = EditGame;
5351       SetGameInfo();
5352     } else {
5353       char buf[MSG_SIZ];
5354       gameMode = MachinePlaysBlack;
5355       StartClocks();
5356       SetGameInfo();
5357       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5358       DisplayTitle(buf);
5359       if (first.sendName) {
5360         sprintf(buf, "name %s\n", gameInfo.white);
5361         SendToProgram(buf, &first);
5362       }
5363       StartClocks();
5364     }
5365     ModeHighlight();
5366   }
5367 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5368   /* Relay move to ICS or chess engine */
5369   if (appData.icsActive) {
5370     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5371         gameMode == IcsExamining) {
5372       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5373       ics_user_moved = 1;
5374     }
5375   } else {
5376     if (first.sendTime && (gameMode == BeginningOfGame ||
5377                            gameMode == MachinePlaysWhite ||
5378                            gameMode == MachinePlaysBlack)) {
5379       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5380     }
5381     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5382          // [HGM] book: if program might be playing, let it use book
5383         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5384         first.maybeThinking = TRUE;
5385     } else SendMoveToProgram(forwardMostMove-1, &first);
5386     if (currentMove == cmailOldMove + 1) {
5387       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5388     }
5389   }
5390
5391   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5392
5393   switch (gameMode) {
5394   case EditGame:
5395     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5396                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5397     case MT_NONE:
5398     case MT_CHECK:
5399       break;
5400     case MT_CHECKMATE:
5401     case MT_STAINMATE:
5402       if (WhiteOnMove(currentMove)) {
5403         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5404       } else {
5405         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5406       }
5407       break;
5408     case MT_STALEMATE:
5409       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5410       break;
5411     }
5412     break;
5413     
5414   case MachinePlaysBlack:
5415   case MachinePlaysWhite:
5416     /* disable certain menu options while machine is thinking */
5417     SetMachineThinkingEnables();
5418     break;
5419
5420   default:
5421     break;
5422   }
5423
5424   if(bookHit) { // [HGM] book: simulate book reply
5425         static char bookMove[MSG_SIZ]; // a bit generous?
5426
5427         programStats.nodes = programStats.depth = programStats.time = 
5428         programStats.score = programStats.got_only_move = 0;
5429         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5430
5431         strcpy(bookMove, "move ");
5432         strcat(bookMove, bookHit);
5433         HandleMachineMove(bookMove, &first);
5434   }
5435   return 1;
5436 }
5437
5438 void
5439 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5440      int fromX, fromY, toX, toY;
5441      int promoChar;
5442 {
5443     /* [HGM] This routine was added to allow calling of its two logical
5444        parts from other modules in the old way. Before, UserMoveEvent()
5445        automatically called FinishMove() if the move was OK, and returned
5446        otherwise. I separated the two, in order to make it possible to
5447        slip a promotion popup in between. But that it always needs two
5448        calls, to the first part, (now called UserMoveTest() ), and to
5449        FinishMove if the first part succeeded. Calls that do not need
5450        to do anything in between, can call this routine the old way. 
5451     */
5452     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5453 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5454     if(moveType == AmbiguousMove)
5455         DrawPosition(FALSE, boards[currentMove]);
5456     else if(moveType != ImpossibleMove && moveType != Comment)
5457         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5458 }
5459
5460 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5461 {
5462 //    char * hint = lastHint;
5463     FrontEndProgramStats stats;
5464
5465     stats.which = cps == &first ? 0 : 1;
5466     stats.depth = cpstats->depth;
5467     stats.nodes = cpstats->nodes;
5468     stats.score = cpstats->score;
5469     stats.time = cpstats->time;
5470     stats.pv = cpstats->movelist;
5471     stats.hint = lastHint;
5472     stats.an_move_index = 0;
5473     stats.an_move_count = 0;
5474
5475     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5476         stats.hint = cpstats->move_name;
5477         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5478         stats.an_move_count = cpstats->nr_moves;
5479     }
5480
5481     SetProgramStats( &stats );
5482 }
5483
5484 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5485 {   // [HGM] book: this routine intercepts moves to simulate book replies
5486     char *bookHit = NULL;
5487
5488     //first determine if the incoming move brings opponent into his book
5489     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5490         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5491     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5492     if(bookHit != NULL && !cps->bookSuspend) {
5493         // make sure opponent is not going to reply after receiving move to book position
5494         SendToProgram("force\n", cps);
5495         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5496     }
5497     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5498     // now arrange restart after book miss
5499     if(bookHit) {
5500         // after a book hit we never send 'go', and the code after the call to this routine
5501         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5502         char buf[MSG_SIZ];
5503         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5504         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5505         SendToProgram(buf, cps);
5506         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5507     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5508         SendToProgram("go\n", cps);
5509         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5510     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5511         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5512             SendToProgram("go\n", cps); 
5513         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5514     }
5515     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5516 }
5517
5518 char *savedMessage;
5519 ChessProgramState *savedState;
5520 void DeferredBookMove(void)
5521 {
5522         if(savedState->lastPing != savedState->lastPong)
5523                     ScheduleDelayedEvent(DeferredBookMove, 10);
5524         else
5525         HandleMachineMove(savedMessage, savedState);
5526 }
5527
5528 void
5529 HandleMachineMove(message, cps)
5530      char *message;
5531      ChessProgramState *cps;
5532 {
5533     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5534     char realname[MSG_SIZ];
5535     int fromX, fromY, toX, toY;
5536     ChessMove moveType;
5537     char promoChar;
5538     char *p;
5539     int machineWhite;
5540     char *bookHit;
5541
5542 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5543     /*
5544      * Kludge to ignore BEL characters
5545      */
5546     while (*message == '\007') message++;
5547
5548     /*
5549      * [HGM] engine debug message: ignore lines starting with '#' character
5550      */
5551     if(cps->debug && *message == '#') return;
5552
5553     /*
5554      * Look for book output
5555      */
5556     if (cps == &first && bookRequested) {
5557         if (message[0] == '\t' || message[0] == ' ') {
5558             /* Part of the book output is here; append it */
5559             strcat(bookOutput, message);
5560             strcat(bookOutput, "  \n");
5561             return;
5562         } else if (bookOutput[0] != NULLCHAR) {
5563             /* All of book output has arrived; display it */
5564             char *p = bookOutput;
5565             while (*p != NULLCHAR) {
5566                 if (*p == '\t') *p = ' ';
5567                 p++;
5568             }
5569             DisplayInformation(bookOutput);
5570             bookRequested = FALSE;
5571             /* Fall through to parse the current output */
5572         }
5573     }
5574
5575     /*
5576      * Look for machine move.
5577      */
5578     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5579         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5580     {
5581         /* This method is only useful on engines that support ping */
5582         if (cps->lastPing != cps->lastPong) {
5583           if (gameMode == BeginningOfGame) {
5584             /* Extra move from before last new; ignore */
5585             if (appData.debugMode) {
5586                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5587             }
5588           } else {
5589             if (appData.debugMode) {
5590                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5591                         cps->which, gameMode);
5592             }
5593
5594             SendToProgram("undo\n", cps);
5595           }
5596           return;
5597         }
5598
5599         switch (gameMode) {
5600           case BeginningOfGame:
5601             /* Extra move from before last reset; ignore */
5602             if (appData.debugMode) {
5603                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5604             }
5605             return;
5606
5607           case EndOfGame:
5608           case IcsIdle:
5609           default:
5610             /* Extra move after we tried to stop.  The mode test is
5611                not a reliable way of detecting this problem, but it's
5612                the best we can do on engines that don't support ping.
5613             */
5614             if (appData.debugMode) {
5615                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5616                         cps->which, gameMode);
5617             }
5618             SendToProgram("undo\n", cps);
5619             return;
5620
5621           case MachinePlaysWhite:
5622           case IcsPlayingWhite:
5623             machineWhite = TRUE;
5624             break;
5625
5626           case MachinePlaysBlack:
5627           case IcsPlayingBlack:
5628             machineWhite = FALSE;
5629             break;
5630
5631           case TwoMachinesPlay:
5632             machineWhite = (cps->twoMachinesColor[0] == 'w');
5633             break;
5634         }
5635         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5636             if (appData.debugMode) {
5637                 fprintf(debugFP,
5638                         "Ignoring move out of turn by %s, gameMode %d"
5639                         ", forwardMost %d\n",
5640                         cps->which, gameMode, forwardMostMove);
5641             }
5642             return;
5643         }
5644
5645     if (appData.debugMode) { int f = forwardMostMove;
5646         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5647                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5648     }
5649         if(cps->alphaRank) AlphaRank(machineMove, 4);
5650         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5651                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5652             /* Machine move could not be parsed; ignore it. */
5653             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5654                     machineMove, cps->which);
5655             DisplayError(buf1, 0);
5656             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5657                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5658             if (gameMode == TwoMachinesPlay) {
5659               GameEnds(machineWhite ? BlackWins : WhiteWins,
5660                        buf1, GE_XBOARD);
5661             }
5662             return;
5663         }
5664
5665         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5666         /* So we have to redo legality test with true e.p. status here,  */
5667         /* to make sure an illegal e.p. capture does not slip through,   */
5668         /* to cause a forfeit on a justified illegal-move complaint      */
5669         /* of the opponent.                                              */
5670         if( gameMode==TwoMachinesPlay && appData.testLegality
5671             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5672                                                               ) {
5673            ChessMove moveType;
5674            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5675                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5676                              fromY, fromX, toY, toX, promoChar);
5677             if (appData.debugMode) {
5678                 int i;
5679                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5680                     castlingRights[forwardMostMove][i], castlingRank[i]);
5681                 fprintf(debugFP, "castling rights\n");
5682             }
5683             if(moveType == IllegalMove) {
5684                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5685                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5686                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5687                            buf1, GE_XBOARD);
5688                 return;
5689            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5690            /* [HGM] Kludge to handle engines that send FRC-style castling
5691               when they shouldn't (like TSCP-Gothic) */
5692            switch(moveType) {
5693              case WhiteASideCastleFR:
5694              case BlackASideCastleFR:
5695                toX+=2;
5696                currentMoveString[2]++;
5697                break;
5698              case WhiteHSideCastleFR:
5699              case BlackHSideCastleFR:
5700                toX--;
5701                currentMoveString[2]--;
5702                break;
5703              default: ; // nothing to do, but suppresses warning of pedantic compilers
5704            }
5705         }
5706         hintRequested = FALSE;
5707         lastHint[0] = NULLCHAR;
5708         bookRequested = FALSE;
5709         /* Program may be pondering now */
5710         cps->maybeThinking = TRUE;
5711         if (cps->sendTime == 2) cps->sendTime = 1;
5712         if (cps->offeredDraw) cps->offeredDraw--;
5713
5714 #if ZIPPY
5715         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5716             first.initDone) {
5717           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5718           ics_user_moved = 1;
5719           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5720                 char buf[3*MSG_SIZ];
5721
5722                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5723                         programStats.score / 100.,
5724                         programStats.depth,
5725                         programStats.time / 100.,
5726                         (unsigned int)programStats.nodes,
5727                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5728                         programStats.movelist);
5729                 SendToICS(buf);
5730 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5731           }
5732         }
5733 #endif
5734         /* currentMoveString is set as a side-effect of ParseOneMove */
5735         strcpy(machineMove, currentMoveString);
5736         strcat(machineMove, "\n");
5737         strcpy(moveList[forwardMostMove], machineMove);
5738
5739         /* [AS] Save move info and clear stats for next move */
5740         pvInfoList[ forwardMostMove ].score = programStats.score;
5741         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5742         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5743         ClearProgramStats();
5744         thinkOutput[0] = NULLCHAR;
5745         hiddenThinkOutputState = 0;
5746
5747         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5748
5749         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5750         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5751             int count = 0;
5752
5753             while( count < adjudicateLossPlies ) {
5754                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5755
5756                 if( count & 1 ) {
5757                     score = -score; /* Flip score for winning side */
5758                 }
5759
5760                 if( score > adjudicateLossThreshold ) {
5761                     break;
5762                 }
5763
5764                 count++;
5765             }
5766
5767             if( count >= adjudicateLossPlies ) {
5768                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5769
5770                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5771                     "Xboard adjudication", 
5772                     GE_XBOARD );
5773
5774                 return;
5775             }
5776         }
5777
5778         if( gameMode == TwoMachinesPlay ) {
5779           // [HGM] some adjudications useful with buggy engines
5780             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5781           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5782
5783
5784             if( appData.testLegality )
5785             {   /* [HGM] Some more adjudications for obstinate engines */
5786                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5787                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5788                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5789                 static int moveCount = 6;
5790                 ChessMove result;
5791                 char *reason = NULL;
5792
5793                 /* Count what is on board. */
5794                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5795                 {   ChessSquare p = boards[forwardMostMove][i][j];
5796                     int m=i;
5797
5798                     switch((int) p)
5799                     {   /* count B,N,R and other of each side */
5800                         case WhiteKing:
5801                         case BlackKing:
5802                              NrK++; break; // [HGM] atomic: count Kings
5803                         case WhiteKnight:
5804                              NrWN++; break;
5805                         case WhiteBishop:
5806                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5807                              bishopsColor |= 1 << ((i^j)&1);
5808                              NrWB++; break;
5809                         case BlackKnight:
5810                              NrBN++; break;
5811                         case BlackBishop:
5812                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5813                              bishopsColor |= 1 << ((i^j)&1);
5814                              NrBB++; break;
5815                         case WhiteRook:
5816                              NrWR++; break;
5817                         case BlackRook:
5818                              NrBR++; break;
5819                         case WhiteQueen:
5820                              NrWQ++; break;
5821                         case BlackQueen:
5822                              NrBQ++; break;
5823                         case EmptySquare: 
5824                              break;
5825                         case BlackPawn:
5826                              m = 7-i;
5827                         case WhitePawn:
5828                              PawnAdvance += m; NrPawns++;
5829                     }
5830                     NrPieces += (p != EmptySquare);
5831                     NrW += ((int)p < (int)BlackPawn);
5832                     if(gameInfo.variant == VariantXiangqi && 
5833                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5834                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5835                         NrW -= ((int)p < (int)BlackPawn);
5836                     }
5837                 }
5838
5839                 /* Some material-based adjudications that have to be made before stalemate test */
5840                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5841                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5842                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5843                      if(appData.checkMates) {
5844                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5845                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5846                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
5847                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
5848                          return;
5849                      }
5850                 }
5851
5852                 /* Bare King in Shatranj (loses) or Losers (wins) */
5853                 if( NrW == 1 || NrPieces - NrW == 1) {
5854                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5855                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
5856                      if(appData.checkMates) {
5857                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5858                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5859                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5860                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5861                          return;
5862                      }
5863                   } else
5864                   if( gameInfo.variant == VariantShatranj && --bare < 0)
5865                   {    /* bare King */
5866                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5867                         if(appData.checkMates) {
5868                             /* but only adjudicate if adjudication enabled */
5869                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5870                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5871                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
5872                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5873                             return;
5874                         }
5875                   }
5876                 } else bare = 1;
5877
5878
5879             // don't wait for engine to announce game end if we can judge ourselves
5880             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5881                                        castlingRights[forwardMostMove]) ) {
5882               case MT_CHECK:
5883                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5884                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
5885                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5886                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5887                             checkCnt++;
5888                         if(checkCnt >= 2) {
5889                             reason = "Xboard adjudication: 3rd check";
5890                             epStatus[forwardMostMove] = EP_CHECKMATE;
5891                             break;
5892                         }
5893                     }
5894                 }
5895               case MT_NONE:
5896               default:
5897                 break;
5898               case MT_STALEMATE:
5899               case MT_STAINMATE:
5900                 reason = "Xboard adjudication: Stalemate";
5901                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5902                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
5903                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5904                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
5905                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5906                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5907                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5908                                                                         EP_CHECKMATE : EP_WINS);
5909                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5910                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5911                 }
5912                 break;
5913               case MT_CHECKMATE:
5914                 reason = "Xboard adjudication: Checkmate";
5915                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5916                 break;
5917             }
5918
5919                 switch(i = epStatus[forwardMostMove]) {
5920                     case EP_STALEMATE:
5921                         result = GameIsDrawn; break;
5922                     case EP_CHECKMATE:
5923                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5924                     case EP_WINS:
5925                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5926                     default:
5927                         result = (ChessMove) 0;
5928                 }
5929                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5930                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5931                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5932                     GameEnds( result, reason, GE_XBOARD );
5933                     return;
5934                 }
5935
5936                 /* Next absolutely insufficient mating material. */
5937                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
5938                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5939                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5940                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5941                 {    /* KBK, KNK, KK of KBKB with like Bishops */
5942
5943                      /* always flag draws, for judging claims */
5944                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
5945
5946                      if(appData.materialDraws) {
5947                          /* but only adjudicate them if adjudication enabled */
5948                          SendToProgram("force\n", cps->other); // suppress reply
5949                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5950                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5951                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5952                          return;
5953                      }
5954                 }
5955
5956                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5957                 if(NrPieces == 4 && 
5958                    (   NrWR == 1 && NrBR == 1 /* KRKR */
5959                    || NrWQ==1 && NrBQ==1     /* KQKQ */
5960                    || NrWN==2 || NrBN==2     /* KNNK */
5961                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5962                   ) ) {
5963                      if(--moveCount < 0 && appData.trivialDraws)
5964                      {    /* if the first 3 moves do not show a tactical win, declare draw */
5965                           SendToProgram("force\n", cps->other); // suppress reply
5966                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5967                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5968                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5969                           return;
5970                      }
5971                 } else moveCount = 6;
5972             }
5973           }
5974 #if 1
5975     if (appData.debugMode) { int i;
5976       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5977               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5978               appData.drawRepeats);
5979       for( i=forwardMostMove; i>=backwardMostMove; i-- )
5980            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5981
5982     }
5983 #endif
5984                 /* Check for rep-draws */
5985                 count = 0;
5986                 for(k = forwardMostMove-2;
5987                     k>=backwardMostMove && k>=forwardMostMove-100 &&
5988                         epStatus[k] < EP_UNKNOWN &&
5989                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5990                     k-=2)
5991                 {   int rights=0;
5992 #if 0
5993     if (appData.debugMode) {
5994       fprintf(debugFP, " loop\n");
5995     }
5996 #endif
5997                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
5998 #if 0
5999     if (appData.debugMode) {
6000       fprintf(debugFP, "match\n");
6001     }
6002 #endif
6003                         /* compare castling rights */
6004                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6005                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6006                                 rights++; /* King lost rights, while rook still had them */
6007                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6008                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6009                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6010                                    rights++; /* but at least one rook lost them */
6011                         }
6012                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6013                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6014                                 rights++; 
6015                         if( castlingRights[forwardMostMove][5] >= 0 ) {
6016                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6017                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6018                                    rights++;
6019                         }
6020 #if 0
6021     if (appData.debugMode) {
6022       for(i=0; i<nrCastlingRights; i++)
6023       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
6024     }
6025
6026     if (appData.debugMode) {
6027       fprintf(debugFP, " %d %d\n", rights, k);
6028     }
6029 #endif
6030                         if( rights == 0 && ++count > appData.drawRepeats-2
6031                             && appData.drawRepeats > 1) {
6032                              /* adjudicate after user-specified nr of repeats */
6033                              SendToProgram("force\n", cps->other); // suppress reply
6034                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6035                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6036                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6037                                 // [HGM] xiangqi: check for forbidden perpetuals
6038                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6039                                 for(m=forwardMostMove; m>k; m-=2) {
6040                                     if(MateTest(boards[m], PosFlags(m), 
6041                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
6042                                         ourPerpetual = 0; // the current mover did not always check
6043                                     if(MateTest(boards[m-1], PosFlags(m-1), 
6044                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
6045                                         hisPerpetual = 0; // the opponent did not always check
6046                                 }
6047                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6048                                                                         ourPerpetual, hisPerpetual);
6049                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6050                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6051                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6052                                     return;
6053                                 }
6054                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6055                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6056                                 // Now check for perpetual chases
6057                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6058                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6059                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6060                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6061                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6062                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6063                                         return;
6064                                     }
6065                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6066                                         break; // Abort repetition-checking loop.
6067                                 }
6068                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6069                              }
6070                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6071                              return;
6072                         }
6073                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6074                              epStatus[forwardMostMove] = EP_REP_DRAW;
6075                     }
6076                 }
6077
6078                 /* Now we test for 50-move draws. Determine ply count */
6079                 count = forwardMostMove;
6080                 /* look for last irreversble move */
6081                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6082                     count--;
6083                 /* if we hit starting position, add initial plies */
6084                 if( count == backwardMostMove )
6085                     count -= initialRulePlies;
6086                 count = forwardMostMove - count; 
6087                 if( count >= 100)
6088                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6089                          /* this is used to judge if draw claims are legal */
6090                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6091                          SendToProgram("force\n", cps->other); // suppress reply
6092                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6093                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6094                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6095                          return;
6096                 }
6097
6098                 /* if draw offer is pending, treat it as a draw claim
6099                  * when draw condition present, to allow engines a way to
6100                  * claim draws before making their move to avoid a race
6101                  * condition occurring after their move
6102                  */
6103                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6104                          char *p = NULL;
6105                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6106                              p = "Draw claim: 50-move rule";
6107                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6108                              p = "Draw claim: 3-fold repetition";
6109                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6110                              p = "Draw claim: insufficient mating material";
6111                          if( p != NULL ) {
6112                              SendToProgram("force\n", cps->other); // suppress reply
6113                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6114                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6115                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6116                              return;
6117                          }
6118                 }
6119
6120
6121                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6122                     SendToProgram("force\n", cps->other); // suppress reply
6123                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6124                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6125
6126                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6127
6128                     return;
6129                 }
6130         }
6131
6132         bookHit = NULL;
6133         if (gameMode == TwoMachinesPlay) {
6134             /* [HGM] relaying draw offers moved to after reception of move */
6135             /* and interpreting offer as claim if it brings draw condition */
6136             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6137                 SendToProgram("draw\n", cps->other);
6138             }
6139             if (cps->other->sendTime) {
6140                 SendTimeRemaining(cps->other,
6141                                   cps->other->twoMachinesColor[0] == 'w');
6142             }
6143             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6144             if (firstMove && !bookHit) {
6145                 firstMove = FALSE;
6146                 if (cps->other->useColors) {
6147                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6148                 }
6149                 SendToProgram("go\n", cps->other);
6150             }
6151             cps->other->maybeThinking = TRUE;
6152         }
6153
6154         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6155         
6156         if (!pausing && appData.ringBellAfterMoves) {
6157             RingBell();
6158         }
6159
6160         /* 
6161          * Reenable menu items that were disabled while
6162          * machine was thinking
6163          */
6164         if (gameMode != TwoMachinesPlay)
6165             SetUserThinkingEnables();
6166
6167         // [HGM] book: after book hit opponent has received move and is now in force mode
6168         // force the book reply into it, and then fake that it outputted this move by jumping
6169         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6170         if(bookHit) {
6171                 static char bookMove[MSG_SIZ]; // a bit generous?
6172
6173                 strcpy(bookMove, "move ");
6174                 strcat(bookMove, bookHit);
6175                 message = bookMove;
6176                 cps = cps->other;
6177                 programStats.nodes = programStats.depth = programStats.time = 
6178                 programStats.score = programStats.got_only_move = 0;
6179                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6180
6181                 if(cps->lastPing != cps->lastPong) {
6182                     savedMessage = message; // args for deferred call
6183                     savedState = cps;
6184                     ScheduleDelayedEvent(DeferredBookMove, 10);
6185                     return;
6186                 }
6187                 goto FakeBookMove;
6188         }
6189
6190         return;
6191     }
6192
6193     /* Set special modes for chess engines.  Later something general
6194      *  could be added here; for now there is just one kludge feature,
6195      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6196      *  when "xboard" is given as an interactive command.
6197      */
6198     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6199         cps->useSigint = FALSE;
6200         cps->useSigterm = FALSE;
6201     }
6202     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6203       ParseFeatures(message+8, cps);
6204       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6205     }
6206
6207     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6208      * want this, I was asked to put it in, and obliged.
6209      */
6210     if (!strncmp(message, "setboard ", 9)) {
6211         Board initial_position; int i;
6212
6213         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6214
6215         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6216             DisplayError(_("Bad FEN received from engine"), 0);
6217             return ;
6218         } else {
6219            Reset(FALSE, FALSE);
6220            CopyBoard(boards[0], initial_position);
6221            initialRulePlies = FENrulePlies;
6222            epStatus[0] = FENepStatus;
6223            for( i=0; i<nrCastlingRights; i++ )
6224                 castlingRights[0][i] = FENcastlingRights[i];
6225            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6226            else gameMode = MachinePlaysBlack;                 
6227            DrawPosition(FALSE, boards[currentMove]);
6228         }
6229         return;
6230     }
6231
6232     /*
6233      * Look for communication commands
6234      */
6235     if (!strncmp(message, "telluser ", 9)) {
6236         DisplayNote(message + 9);
6237         return;
6238     }
6239     if (!strncmp(message, "tellusererror ", 14)) {
6240         DisplayError(message + 14, 0);
6241         return;
6242     }
6243     if (!strncmp(message, "tellopponent ", 13)) {
6244       if (appData.icsActive) {
6245         if (loggedOn) {
6246           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6247           SendToICS(buf1);
6248         }
6249       } else {
6250         DisplayNote(message + 13);
6251       }
6252       return;
6253     }
6254     if (!strncmp(message, "tellothers ", 11)) {
6255       if (appData.icsActive) {
6256         if (loggedOn) {
6257           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6258           SendToICS(buf1);
6259         }
6260       }
6261       return;
6262     }
6263     if (!strncmp(message, "tellall ", 8)) {
6264       if (appData.icsActive) {
6265         if (loggedOn) {
6266           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6267           SendToICS(buf1);
6268         }
6269       } else {
6270         DisplayNote(message + 8);
6271       }
6272       return;
6273     }
6274     if (strncmp(message, "warning", 7) == 0) {
6275         /* Undocumented feature, use tellusererror in new code */
6276         DisplayError(message, 0);
6277         return;
6278     }
6279     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6280         strcpy(realname, cps->tidy);
6281         strcat(realname, " query");
6282         AskQuestion(realname, buf2, buf1, cps->pr);
6283         return;
6284     }
6285     /* Commands from the engine directly to ICS.  We don't allow these to be 
6286      *  sent until we are logged on. Crafty kibitzes have been known to 
6287      *  interfere with the login process.
6288      */
6289     if (loggedOn) {
6290         if (!strncmp(message, "tellics ", 8)) {
6291             SendToICS(message + 8);
6292             SendToICS("\n");
6293             return;
6294         }
6295         if (!strncmp(message, "tellicsnoalias ", 15)) {
6296             SendToICS(ics_prefix);
6297             SendToICS(message + 15);
6298             SendToICS("\n");
6299             return;
6300         }
6301         /* The following are for backward compatibility only */
6302         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6303             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6304             SendToICS(ics_prefix);
6305             SendToICS(message);
6306             SendToICS("\n");
6307             return;
6308         }
6309     }
6310     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6311         return;
6312     }
6313     /*
6314      * If the move is illegal, cancel it and redraw the board.
6315      * Also deal with other error cases.  Matching is rather loose
6316      * here to accommodate engines written before the spec.
6317      */
6318     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6319         strncmp(message, "Error", 5) == 0) {
6320         if (StrStr(message, "name") || 
6321             StrStr(message, "rating") || StrStr(message, "?") ||
6322             StrStr(message, "result") || StrStr(message, "board") ||
6323             StrStr(message, "bk") || StrStr(message, "computer") ||
6324             StrStr(message, "variant") || StrStr(message, "hint") ||
6325             StrStr(message, "random") || StrStr(message, "depth") ||
6326             StrStr(message, "accepted")) {
6327             return;
6328         }
6329         if (StrStr(message, "protover")) {
6330           /* Program is responding to input, so it's apparently done
6331              initializing, and this error message indicates it is
6332              protocol version 1.  So we don't need to wait any longer
6333              for it to initialize and send feature commands. */
6334           FeatureDone(cps, 1);
6335           cps->protocolVersion = 1;
6336           return;
6337         }
6338         cps->maybeThinking = FALSE;
6339
6340         if (StrStr(message, "draw")) {
6341             /* Program doesn't have "draw" command */
6342             cps->sendDrawOffers = 0;
6343             return;
6344         }
6345         if (cps->sendTime != 1 &&
6346             (StrStr(message, "time") || StrStr(message, "otim"))) {
6347           /* Program apparently doesn't have "time" or "otim" command */
6348           cps->sendTime = 0;
6349           return;
6350         }
6351         if (StrStr(message, "analyze")) {
6352             cps->analysisSupport = FALSE;
6353             cps->analyzing = FALSE;
6354             Reset(FALSE, TRUE);
6355             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6356             DisplayError(buf2, 0);
6357             return;
6358         }
6359         if (StrStr(message, "(no matching move)st")) {
6360           /* Special kludge for GNU Chess 4 only */
6361           cps->stKludge = TRUE;
6362           SendTimeControl(cps, movesPerSession, timeControl,
6363                           timeIncrement, appData.searchDepth,
6364                           searchTime);
6365           return;
6366         }
6367         if (StrStr(message, "(no matching move)sd")) {
6368           /* Special kludge for GNU Chess 4 only */
6369           cps->sdKludge = TRUE;
6370           SendTimeControl(cps, movesPerSession, timeControl,
6371                           timeIncrement, appData.searchDepth,
6372                           searchTime);
6373           return;
6374         }
6375         if (!StrStr(message, "llegal")) {
6376             return;
6377         }
6378         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6379             gameMode == IcsIdle) return;
6380         if (forwardMostMove <= backwardMostMove) return;
6381 #if 0
6382         /* Following removed: it caused a bug where a real illegal move
6383            message in analyze mored would be ignored. */
6384         if (cps == &first && programStats.ok_to_send == 0) {
6385             /* Bogus message from Crafty responding to "."  This filtering
6386                can miss some of the bad messages, but fortunately the bug 
6387                is fixed in current Crafty versions, so it doesn't matter. */
6388             return;
6389         }
6390 #endif
6391         if (pausing) PauseEvent();
6392       if(appData.forceIllegal) {
6393             // [HGM] illegal: machine refused move; force position after move into it
6394           SendToProgram("force\n", cps);
6395           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6396                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6397                 // when black is to move, while there might be nothing on a2 or black
6398                 // might already have the move. So send the board as if white has the move.
6399                 // But first we must change the stm of the engine, as it refused the last move
6400                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6401                 if(WhiteOnMove(forwardMostMove)) {
6402                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6403                     SendBoard(cps, forwardMostMove); // kludgeless board
6404                 } else {
6405                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6406                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6407                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6408                 }
6409           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6410             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6411                  gameMode == TwoMachinesPlay)
6412               SendToProgram("go\n", cps);
6413             return;
6414       } else
6415         if (gameMode == PlayFromGameFile) {
6416             /* Stop reading this game file */
6417             gameMode = EditGame;
6418             ModeHighlight();
6419         }
6420         currentMove = --forwardMostMove;
6421         DisplayMove(currentMove-1); /* before DisplayMoveError */
6422         SwitchClocks();
6423         DisplayBothClocks();
6424         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6425                 parseList[currentMove], cps->which);
6426         DisplayMoveError(buf1);
6427         DrawPosition(FALSE, boards[currentMove]);
6428
6429         /* [HGM] illegal-move claim should forfeit game when Xboard */
6430         /* only passes fully legal moves                            */
6431         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6432             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6433                                 "False illegal-move claim", GE_XBOARD );
6434         }
6435         return;
6436     }
6437     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6438         /* Program has a broken "time" command that
6439            outputs a string not ending in newline.
6440            Don't use it. */
6441         cps->sendTime = 0;
6442     }
6443     
6444     /*
6445      * If chess program startup fails, exit with an error message.
6446      * Attempts to recover here are futile.
6447      */
6448     if ((StrStr(message, "unknown host") != NULL)
6449         || (StrStr(message, "No remote directory") != NULL)
6450         || (StrStr(message, "not found") != NULL)
6451         || (StrStr(message, "No such file") != NULL)
6452         || (StrStr(message, "can't alloc") != NULL)
6453         || (StrStr(message, "Permission denied") != NULL)) {
6454
6455         cps->maybeThinking = FALSE;
6456         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6457                 cps->which, cps->program, cps->host, message);
6458         RemoveInputSource(cps->isr);
6459         DisplayFatalError(buf1, 0, 1);
6460         return;
6461     }
6462     
6463     /* 
6464      * Look for hint output
6465      */
6466     if (sscanf(message, "Hint: %s", buf1) == 1) {
6467         if (cps == &first && hintRequested) {
6468             hintRequested = FALSE;
6469             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6470                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6471                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6472                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6473                                     fromY, fromX, toY, toX, promoChar, buf1);
6474                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6475                 DisplayInformation(buf2);
6476             } else {
6477                 /* Hint move could not be parsed!? */
6478               snprintf(buf2, sizeof(buf2),
6479                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6480                         buf1, cps->which);
6481                 DisplayError(buf2, 0);
6482             }
6483         } else {
6484             strcpy(lastHint, buf1);
6485         }
6486         return;
6487     }
6488
6489     /*
6490      * Ignore other messages if game is not in progress
6491      */
6492     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6493         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6494
6495     /*
6496      * look for win, lose, draw, or draw offer
6497      */
6498     if (strncmp(message, "1-0", 3) == 0) {
6499         char *p, *q, *r = "";
6500         p = strchr(message, '{');
6501         if (p) {
6502             q = strchr(p, '}');
6503             if (q) {
6504                 *q = NULLCHAR;
6505                 r = p + 1;
6506             }
6507         }
6508         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6509         return;
6510     } else if (strncmp(message, "0-1", 3) == 0) {
6511         char *p, *q, *r = "";
6512         p = strchr(message, '{');
6513         if (p) {
6514             q = strchr(p, '}');
6515             if (q) {
6516                 *q = NULLCHAR;
6517                 r = p + 1;
6518             }
6519         }
6520         /* Kludge for Arasan 4.1 bug */
6521         if (strcmp(r, "Black resigns") == 0) {
6522             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6523             return;
6524         }
6525         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6526         return;
6527     } else if (strncmp(message, "1/2", 3) == 0) {
6528         char *p, *q, *r = "";
6529         p = strchr(message, '{');
6530         if (p) {
6531             q = strchr(p, '}');
6532             if (q) {
6533                 *q = NULLCHAR;
6534                 r = p + 1;
6535             }
6536         }
6537             
6538         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6539         return;
6540
6541     } else if (strncmp(message, "White resign", 12) == 0) {
6542         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6543         return;
6544     } else if (strncmp(message, "Black resign", 12) == 0) {
6545         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6546         return;
6547     } else if (strncmp(message, "White matches", 13) == 0 ||
6548                strncmp(message, "Black matches", 13) == 0   ) {
6549         /* [HGM] ignore GNUShogi noises */
6550         return;
6551     } else if (strncmp(message, "White", 5) == 0 &&
6552                message[5] != '(' &&
6553                StrStr(message, "Black") == NULL) {
6554         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6555         return;
6556     } else if (strncmp(message, "Black", 5) == 0 &&
6557                message[5] != '(') {
6558         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6559         return;
6560     } else if (strcmp(message, "resign") == 0 ||
6561                strcmp(message, "computer resigns") == 0) {
6562         switch (gameMode) {
6563           case MachinePlaysBlack:
6564           case IcsPlayingBlack:
6565             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6566             break;
6567           case MachinePlaysWhite:
6568           case IcsPlayingWhite:
6569             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6570             break;
6571           case TwoMachinesPlay:
6572             if (cps->twoMachinesColor[0] == 'w')
6573               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6574             else
6575               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6576             break;
6577           default:
6578             /* can't happen */
6579             break;
6580         }
6581         return;
6582     } else if (strncmp(message, "opponent mates", 14) == 0) {
6583         switch (gameMode) {
6584           case MachinePlaysBlack:
6585           case IcsPlayingBlack:
6586             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6587             break;
6588           case MachinePlaysWhite:
6589           case IcsPlayingWhite:
6590             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6591             break;
6592           case TwoMachinesPlay:
6593             if (cps->twoMachinesColor[0] == 'w')
6594               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6595             else
6596               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6597             break;
6598           default:
6599             /* can't happen */
6600             break;
6601         }
6602         return;
6603     } else if (strncmp(message, "computer mates", 14) == 0) {
6604         switch (gameMode) {
6605           case MachinePlaysBlack:
6606           case IcsPlayingBlack:
6607             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6608             break;
6609           case MachinePlaysWhite:
6610           case IcsPlayingWhite:
6611             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6612             break;
6613           case TwoMachinesPlay:
6614             if (cps->twoMachinesColor[0] == 'w')
6615               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6616             else
6617               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6618             break;
6619           default:
6620             /* can't happen */
6621             break;
6622         }
6623         return;
6624     } else if (strncmp(message, "checkmate", 9) == 0) {
6625         if (WhiteOnMove(forwardMostMove)) {
6626             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6627         } else {
6628             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6629         }
6630         return;
6631     } else if (strstr(message, "Draw") != NULL ||
6632                strstr(message, "game is a draw") != NULL) {
6633         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6634         return;
6635     } else if (strstr(message, "offer") != NULL &&
6636                strstr(message, "draw") != NULL) {
6637 #if ZIPPY
6638         if (appData.zippyPlay && first.initDone) {
6639             /* Relay offer to ICS */
6640             SendToICS(ics_prefix);
6641             SendToICS("draw\n");
6642         }
6643 #endif
6644         cps->offeredDraw = 2; /* valid until this engine moves twice */
6645         if (gameMode == TwoMachinesPlay) {
6646             if (cps->other->offeredDraw) {
6647                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6648             /* [HGM] in two-machine mode we delay relaying draw offer      */
6649             /* until after we also have move, to see if it is really claim */
6650             }
6651 #if 0
6652               else {
6653                 if (cps->other->sendDrawOffers) {
6654                     SendToProgram("draw\n", cps->other);
6655                 }
6656             }
6657 #endif
6658         } else if (gameMode == MachinePlaysWhite ||
6659                    gameMode == MachinePlaysBlack) {
6660           if (userOfferedDraw) {
6661             DisplayInformation(_("Machine accepts your draw offer"));
6662             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6663           } else {
6664             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6665           }
6666         }
6667     }
6668
6669     
6670     /*
6671      * Look for thinking output
6672      */
6673     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6674           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6675                                 ) {
6676         int plylev, mvleft, mvtot, curscore, time;
6677         char mvname[MOVE_LEN];
6678         u64 nodes; // [DM]
6679         char plyext;
6680         int ignore = FALSE;
6681         int prefixHint = FALSE;
6682         mvname[0] = NULLCHAR;
6683
6684         switch (gameMode) {
6685           case MachinePlaysBlack:
6686           case IcsPlayingBlack:
6687             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6688             break;
6689           case MachinePlaysWhite:
6690           case IcsPlayingWhite:
6691             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6692             break;
6693           case AnalyzeMode:
6694           case AnalyzeFile:
6695             break;
6696           case IcsObserving: /* [DM] icsEngineAnalyze */
6697             if (!appData.icsEngineAnalyze) ignore = TRUE;
6698             break;
6699           case TwoMachinesPlay:
6700             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6701                 ignore = TRUE;
6702             }
6703             break;
6704           default:
6705             ignore = TRUE;
6706             break;
6707         }
6708
6709         if (!ignore) {
6710             buf1[0] = NULLCHAR;
6711             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6712                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6713
6714                 if (plyext != ' ' && plyext != '\t') {
6715                     time *= 100;
6716                 }
6717
6718                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6719                 if( cps->scoreIsAbsolute && 
6720                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6721                 {
6722                     curscore = -curscore;
6723                 }
6724
6725
6726                 programStats.depth = plylev;
6727                 programStats.nodes = nodes;
6728                 programStats.time = time;
6729                 programStats.score = curscore;
6730                 programStats.got_only_move = 0;
6731
6732                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6733                         int ticklen;
6734
6735                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6736                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6737                         if(WhiteOnMove(forwardMostMove)) 
6738                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6739                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6740                 }
6741
6742                 /* Buffer overflow protection */
6743                 if (buf1[0] != NULLCHAR) {
6744                     if (strlen(buf1) >= sizeof(programStats.movelist)
6745                         && appData.debugMode) {
6746                         fprintf(debugFP,
6747                                 "PV is too long; using the first %d bytes.\n",
6748                                 sizeof(programStats.movelist) - 1);
6749                     }
6750
6751                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6752                 } else {
6753                     sprintf(programStats.movelist, " no PV\n");
6754                 }
6755
6756                 if (programStats.seen_stat) {
6757                     programStats.ok_to_send = 1;
6758                 }
6759
6760                 if (strchr(programStats.movelist, '(') != NULL) {
6761                     programStats.line_is_book = 1;
6762                     programStats.nr_moves = 0;
6763                     programStats.moves_left = 0;
6764                 } else {
6765                     programStats.line_is_book = 0;
6766                 }
6767
6768                 SendProgramStatsToFrontend( cps, &programStats );
6769
6770                 /* 
6771                     [AS] Protect the thinkOutput buffer from overflow... this
6772                     is only useful if buf1 hasn't overflowed first!
6773                 */
6774                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6775                         plylev, 
6776                         (gameMode == TwoMachinesPlay ?
6777                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6778                         ((double) curscore) / 100.0,
6779                         prefixHint ? lastHint : "",
6780                         prefixHint ? " " : "" );
6781
6782                 if( buf1[0] != NULLCHAR ) {
6783                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6784
6785                     if( strlen(buf1) > max_len ) {
6786                         if( appData.debugMode) {
6787                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6788                         }
6789                         buf1[max_len+1] = '\0';
6790                     }
6791
6792                     strcat( thinkOutput, buf1 );
6793                 }
6794
6795                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6796                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6797                     DisplayMove(currentMove - 1);
6798                     DisplayAnalysis();
6799                 }
6800                 return;
6801
6802             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6803                 /* crafty (9.25+) says "(only move) <move>"
6804                  * if there is only 1 legal move
6805                  */
6806                 sscanf(p, "(only move) %s", buf1);
6807                 sprintf(thinkOutput, "%s (only move)", buf1);
6808                 sprintf(programStats.movelist, "%s (only move)", buf1);
6809                 programStats.depth = 1;
6810                 programStats.nr_moves = 1;
6811                 programStats.moves_left = 1;
6812                 programStats.nodes = 1;
6813                 programStats.time = 1;
6814                 programStats.got_only_move = 1;
6815
6816                 /* Not really, but we also use this member to
6817                    mean "line isn't going to change" (Crafty
6818                    isn't searching, so stats won't change) */
6819                 programStats.line_is_book = 1;
6820
6821                 SendProgramStatsToFrontend( cps, &programStats );
6822                 
6823                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
6824                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6825                     DisplayMove(currentMove - 1);
6826                     DisplayAnalysis();
6827                 }
6828                 return;
6829             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6830                               &time, &nodes, &plylev, &mvleft,
6831                               &mvtot, mvname) >= 5) {
6832                 /* The stat01: line is from Crafty (9.29+) in response
6833                    to the "." command */
6834                 programStats.seen_stat = 1;
6835                 cps->maybeThinking = TRUE;
6836
6837                 if (programStats.got_only_move || !appData.periodicUpdates)
6838                   return;
6839
6840                 programStats.depth = plylev;
6841                 programStats.time = time;
6842                 programStats.nodes = nodes;
6843                 programStats.moves_left = mvleft;
6844                 programStats.nr_moves = mvtot;
6845                 strcpy(programStats.move_name, mvname);
6846                 programStats.ok_to_send = 1;
6847                 programStats.movelist[0] = '\0';
6848
6849                 SendProgramStatsToFrontend( cps, &programStats );
6850
6851                 DisplayAnalysis();
6852                 return;
6853
6854             } else if (strncmp(message,"++",2) == 0) {
6855                 /* Crafty 9.29+ outputs this */
6856                 programStats.got_fail = 2;
6857                 return;
6858
6859             } else if (strncmp(message,"--",2) == 0) {
6860                 /* Crafty 9.29+ outputs this */
6861                 programStats.got_fail = 1;
6862                 return;
6863
6864             } else if (thinkOutput[0] != NULLCHAR &&
6865                        strncmp(message, "    ", 4) == 0) {
6866                 unsigned message_len;
6867
6868                 p = message;
6869                 while (*p && *p == ' ') p++;
6870
6871                 message_len = strlen( p );
6872
6873                 /* [AS] Avoid buffer overflow */
6874                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6875                     strcat(thinkOutput, " ");
6876                     strcat(thinkOutput, p);
6877                 }
6878
6879                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6880                     strcat(programStats.movelist, " ");
6881                     strcat(programStats.movelist, p);
6882                 }
6883
6884                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6885                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6886                     DisplayMove(currentMove - 1);
6887                     DisplayAnalysis();
6888                 }
6889                 return;
6890             }
6891         }
6892         else {
6893             buf1[0] = NULLCHAR;
6894
6895             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6896                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
6897             {
6898                 ChessProgramStats cpstats;
6899
6900                 if (plyext != ' ' && plyext != '\t') {
6901                     time *= 100;
6902                 }
6903
6904                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6905                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6906                     curscore = -curscore;
6907                 }
6908
6909                 cpstats.depth = plylev;
6910                 cpstats.nodes = nodes;
6911                 cpstats.time = time;
6912                 cpstats.score = curscore;
6913                 cpstats.got_only_move = 0;
6914                 cpstats.movelist[0] = '\0';
6915
6916                 if (buf1[0] != NULLCHAR) {
6917                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6918                 }
6919
6920                 cpstats.ok_to_send = 0;
6921                 cpstats.line_is_book = 0;
6922                 cpstats.nr_moves = 0;
6923                 cpstats.moves_left = 0;
6924
6925                 SendProgramStatsToFrontend( cps, &cpstats );
6926             }
6927         }
6928     }
6929 }
6930
6931
6932 /* Parse a game score from the character string "game", and
6933    record it as the history of the current game.  The game
6934    score is NOT assumed to start from the standard position. 
6935    The display is not updated in any way.
6936    */
6937 void
6938 ParseGameHistory(game)
6939      char *game;
6940 {
6941     ChessMove moveType;
6942     int fromX, fromY, toX, toY, boardIndex;
6943     char promoChar;
6944     char *p, *q;
6945     char buf[MSG_SIZ];
6946
6947     if (appData.debugMode)
6948       fprintf(debugFP, "Parsing game history: %s\n", game);
6949
6950     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6951     gameInfo.site = StrSave(appData.icsHost);
6952     gameInfo.date = PGNDate();
6953     gameInfo.round = StrSave("-");
6954
6955     /* Parse out names of players */
6956     while (*game == ' ') game++;
6957     p = buf;
6958     while (*game != ' ') *p++ = *game++;
6959     *p = NULLCHAR;
6960     gameInfo.white = StrSave(buf);
6961     while (*game == ' ') game++;
6962     p = buf;
6963     while (*game != ' ' && *game != '\n') *p++ = *game++;
6964     *p = NULLCHAR;
6965     gameInfo.black = StrSave(buf);
6966
6967     /* Parse moves */
6968     boardIndex = blackPlaysFirst ? 1 : 0;
6969     yynewstr(game);
6970     for (;;) {
6971         yyboardindex = boardIndex;
6972         moveType = (ChessMove) yylex();
6973         switch (moveType) {
6974           case IllegalMove:             /* maybe suicide chess, etc. */
6975   if (appData.debugMode) {
6976     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6977     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6978     setbuf(debugFP, NULL);
6979   }
6980           case WhitePromotionChancellor:
6981           case BlackPromotionChancellor:
6982           case WhitePromotionArchbishop:
6983           case BlackPromotionArchbishop:
6984           case WhitePromotionQueen:
6985           case BlackPromotionQueen:
6986           case WhitePromotionRook:
6987           case BlackPromotionRook:
6988           case WhitePromotionBishop:
6989           case BlackPromotionBishop:
6990           case WhitePromotionKnight:
6991           case BlackPromotionKnight:
6992           case WhitePromotionKing:
6993           case BlackPromotionKing:
6994           case NormalMove:
6995           case WhiteCapturesEnPassant:
6996           case BlackCapturesEnPassant:
6997           case WhiteKingSideCastle:
6998           case WhiteQueenSideCastle:
6999           case BlackKingSideCastle:
7000           case BlackQueenSideCastle:
7001           case WhiteKingSideCastleWild:
7002           case WhiteQueenSideCastleWild:
7003           case BlackKingSideCastleWild:
7004           case BlackQueenSideCastleWild:
7005           /* PUSH Fabien */
7006           case WhiteHSideCastleFR:
7007           case WhiteASideCastleFR:
7008           case BlackHSideCastleFR:
7009           case BlackASideCastleFR:
7010           /* POP Fabien */
7011             fromX = currentMoveString[0] - AAA;
7012             fromY = currentMoveString[1] - ONE;
7013             toX = currentMoveString[2] - AAA;
7014             toY = currentMoveString[3] - ONE;
7015             promoChar = currentMoveString[4];
7016             break;
7017           case WhiteDrop:
7018           case BlackDrop:
7019             fromX = moveType == WhiteDrop ?
7020               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7021             (int) CharToPiece(ToLower(currentMoveString[0]));
7022             fromY = DROP_RANK;
7023             toX = currentMoveString[2] - AAA;
7024             toY = currentMoveString[3] - ONE;
7025             promoChar = NULLCHAR;
7026             break;
7027           case AmbiguousMove:
7028             /* bug? */
7029             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7030   if (appData.debugMode) {
7031     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7032     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7033     setbuf(debugFP, NULL);
7034   }
7035             DisplayError(buf, 0);
7036             return;
7037           case ImpossibleMove:
7038             /* bug? */
7039             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7040   if (appData.debugMode) {
7041     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7042     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7043     setbuf(debugFP, NULL);
7044   }
7045             DisplayError(buf, 0);
7046             return;
7047           case (ChessMove) 0:   /* end of file */
7048             if (boardIndex < backwardMostMove) {
7049                 /* Oops, gap.  How did that happen? */
7050                 DisplayError(_("Gap in move list"), 0);
7051                 return;
7052             }
7053             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7054             if (boardIndex > forwardMostMove) {
7055                 forwardMostMove = boardIndex;
7056             }
7057             return;
7058           case ElapsedTime:
7059             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7060                 strcat(parseList[boardIndex-1], " ");
7061                 strcat(parseList[boardIndex-1], yy_text);
7062             }
7063             continue;
7064           case Comment:
7065           case PGNTag:
7066           case NAG:
7067           default:
7068             /* ignore */
7069             continue;
7070           case WhiteWins:
7071           case BlackWins:
7072           case GameIsDrawn:
7073           case GameUnfinished:
7074             if (gameMode == IcsExamining) {
7075                 if (boardIndex < backwardMostMove) {
7076                     /* Oops, gap.  How did that happen? */
7077                     return;
7078                 }
7079                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7080                 return;
7081             }
7082             gameInfo.result = moveType;
7083             p = strchr(yy_text, '{');
7084             if (p == NULL) p = strchr(yy_text, '(');
7085             if (p == NULL) {
7086                 p = yy_text;
7087                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7088             } else {
7089                 q = strchr(p, *p == '{' ? '}' : ')');
7090                 if (q != NULL) *q = NULLCHAR;
7091                 p++;
7092             }
7093             gameInfo.resultDetails = StrSave(p);
7094             continue;
7095         }
7096         if (boardIndex >= forwardMostMove &&
7097             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7098             backwardMostMove = blackPlaysFirst ? 1 : 0;
7099             return;
7100         }
7101         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7102                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7103                                  parseList[boardIndex]);
7104         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7105         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7106         /* currentMoveString is set as a side-effect of yylex */
7107         strcpy(moveList[boardIndex], currentMoveString);
7108         strcat(moveList[boardIndex], "\n");
7109         boardIndex++;
7110         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7111                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7112         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7113                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7114           case MT_NONE:
7115           case MT_STALEMATE:
7116           default:
7117             break;
7118           case MT_CHECK:
7119             if(gameInfo.variant != VariantShogi)
7120                 strcat(parseList[boardIndex - 1], "+");
7121             break;
7122           case MT_CHECKMATE:
7123           case MT_STAINMATE:
7124             strcat(parseList[boardIndex - 1], "#");
7125             break;
7126         }
7127     }
7128 }
7129
7130
7131 /* Apply a move to the given board  */
7132 void
7133 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7134      int fromX, fromY, toX, toY;
7135      int promoChar;
7136      Board board;
7137      char *castling;
7138      char *ep;
7139 {
7140   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7141
7142     /* [HGM] compute & store e.p. status and castling rights for new position */
7143     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7144     { int i;
7145
7146       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7147       oldEP = *ep;
7148       *ep = EP_NONE;
7149
7150       if( board[toY][toX] != EmptySquare ) 
7151            *ep = EP_CAPTURE;  
7152
7153       if( board[fromY][fromX] == WhitePawn ) {
7154            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7155                *ep = EP_PAWN_MOVE;
7156            if( toY-fromY==2) {
7157                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7158                         gameInfo.variant != VariantBerolina || toX < fromX)
7159                       *ep = toX | berolina;
7160                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7161                         gameInfo.variant != VariantBerolina || toX > fromX) 
7162                       *ep = toX;
7163            }
7164       } else 
7165       if( board[fromY][fromX] == BlackPawn ) {
7166            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7167                *ep = EP_PAWN_MOVE; 
7168            if( toY-fromY== -2) {
7169                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7170                         gameInfo.variant != VariantBerolina || toX < fromX)
7171                       *ep = toX | berolina;
7172                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7173                         gameInfo.variant != VariantBerolina || toX > fromX) 
7174                       *ep = toX;
7175            }
7176        }
7177
7178        for(i=0; i<nrCastlingRights; i++) {
7179            if(castling[i] == fromX && castlingRank[i] == fromY ||
7180               castling[i] == toX   && castlingRank[i] == toY   
7181              ) castling[i] = -1; // revoke for moved or captured piece
7182        }
7183
7184     }
7185
7186   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7187   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7188        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7189          
7190   if (fromX == toX && fromY == toY) return;
7191
7192   if (fromY == DROP_RANK) {
7193         /* must be first */
7194         piece = board[toY][toX] = (ChessSquare) fromX;
7195   } else {
7196      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7197      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7198      if(gameInfo.variant == VariantKnightmate)
7199          king += (int) WhiteUnicorn - (int) WhiteKing;
7200
7201     /* Code added by Tord: */
7202     /* FRC castling assumed when king captures friendly rook. */
7203     if (board[fromY][fromX] == WhiteKing &&
7204              board[toY][toX] == WhiteRook) {
7205       board[fromY][fromX] = EmptySquare;
7206       board[toY][toX] = EmptySquare;
7207       if(toX > fromX) {
7208         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7209       } else {
7210         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7211       }
7212     } else if (board[fromY][fromX] == BlackKing &&
7213                board[toY][toX] == BlackRook) {
7214       board[fromY][fromX] = EmptySquare;
7215       board[toY][toX] = EmptySquare;
7216       if(toX > fromX) {
7217         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7218       } else {
7219         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7220       }
7221     /* End of code added by Tord */
7222
7223     } else if (board[fromY][fromX] == king
7224         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7225         && toY == fromY && toX > fromX+1) {
7226         board[fromY][fromX] = EmptySquare;
7227         board[toY][toX] = king;
7228         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7229         board[fromY][BOARD_RGHT-1] = EmptySquare;
7230     } else if (board[fromY][fromX] == king
7231         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7232                && toY == fromY && toX < fromX-1) {
7233         board[fromY][fromX] = EmptySquare;
7234         board[toY][toX] = king;
7235         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7236         board[fromY][BOARD_LEFT] = EmptySquare;
7237     } else if (board[fromY][fromX] == WhitePawn
7238                && toY == BOARD_HEIGHT-1
7239                && gameInfo.variant != VariantXiangqi
7240                ) {
7241         /* white pawn promotion */
7242         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7243         if (board[toY][toX] == EmptySquare) {
7244             board[toY][toX] = WhiteQueen;
7245         }
7246         if(gameInfo.variant==VariantBughouse ||
7247            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7248             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7249         board[fromY][fromX] = EmptySquare;
7250     } else if ((fromY == BOARD_HEIGHT-4)
7251                && (toX != fromX)
7252                && gameInfo.variant != VariantXiangqi
7253                && gameInfo.variant != VariantBerolina
7254                && (board[fromY][fromX] == WhitePawn)
7255                && (board[toY][toX] == EmptySquare)) {
7256         board[fromY][fromX] = EmptySquare;
7257         board[toY][toX] = WhitePawn;
7258         captured = board[toY - 1][toX];
7259         board[toY - 1][toX] = EmptySquare;
7260     } else if ((fromY == BOARD_HEIGHT-4)
7261                && (toX == fromX)
7262                && gameInfo.variant == VariantBerolina
7263                && (board[fromY][fromX] == WhitePawn)
7264                && (board[toY][toX] == EmptySquare)) {
7265         board[fromY][fromX] = EmptySquare;
7266         board[toY][toX] = WhitePawn;
7267         if(oldEP & EP_BEROLIN_A) {
7268                 captured = board[fromY][fromX-1];
7269                 board[fromY][fromX-1] = EmptySquare;
7270         }else{  captured = board[fromY][fromX+1];
7271                 board[fromY][fromX+1] = EmptySquare;
7272         }
7273     } else if (board[fromY][fromX] == king
7274         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7275                && toY == fromY && toX > fromX+1) {
7276         board[fromY][fromX] = EmptySquare;
7277         board[toY][toX] = king;
7278         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7279         board[fromY][BOARD_RGHT-1] = EmptySquare;
7280     } else if (board[fromY][fromX] == king
7281         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7282                && toY == fromY && toX < fromX-1) {
7283         board[fromY][fromX] = EmptySquare;
7284         board[toY][toX] = king;
7285         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7286         board[fromY][BOARD_LEFT] = EmptySquare;
7287     } else if (fromY == 7 && fromX == 3
7288                && board[fromY][fromX] == BlackKing
7289                && toY == 7 && toX == 5) {
7290         board[fromY][fromX] = EmptySquare;
7291         board[toY][toX] = BlackKing;
7292         board[fromY][7] = EmptySquare;
7293         board[toY][4] = BlackRook;
7294     } else if (fromY == 7 && fromX == 3
7295                && board[fromY][fromX] == BlackKing
7296                && toY == 7 && toX == 1) {
7297         board[fromY][fromX] = EmptySquare;
7298         board[toY][toX] = BlackKing;
7299         board[fromY][0] = EmptySquare;
7300         board[toY][2] = BlackRook;
7301     } else if (board[fromY][fromX] == BlackPawn
7302                && toY == 0
7303                && gameInfo.variant != VariantXiangqi
7304                ) {
7305         /* black pawn promotion */
7306         board[0][toX] = CharToPiece(ToLower(promoChar));
7307         if (board[0][toX] == EmptySquare) {
7308             board[0][toX] = BlackQueen;
7309         }
7310         if(gameInfo.variant==VariantBughouse ||
7311            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7312             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7313         board[fromY][fromX] = EmptySquare;
7314     } else if ((fromY == 3)
7315                && (toX != fromX)
7316                && gameInfo.variant != VariantXiangqi
7317                && gameInfo.variant != VariantBerolina
7318                && (board[fromY][fromX] == BlackPawn)
7319                && (board[toY][toX] == EmptySquare)) {
7320         board[fromY][fromX] = EmptySquare;
7321         board[toY][toX] = BlackPawn;
7322         captured = board[toY + 1][toX];
7323         board[toY + 1][toX] = EmptySquare;
7324     } else if ((fromY == 3)
7325                && (toX == fromX)
7326                && gameInfo.variant == VariantBerolina
7327                && (board[fromY][fromX] == BlackPawn)
7328                && (board[toY][toX] == EmptySquare)) {
7329         board[fromY][fromX] = EmptySquare;
7330         board[toY][toX] = BlackPawn;
7331         if(oldEP & EP_BEROLIN_A) {
7332                 captured = board[fromY][fromX-1];
7333                 board[fromY][fromX-1] = EmptySquare;
7334         }else{  captured = board[fromY][fromX+1];
7335                 board[fromY][fromX+1] = EmptySquare;
7336         }
7337     } else {
7338         board[toY][toX] = board[fromY][fromX];
7339         board[fromY][fromX] = EmptySquare;
7340     }
7341
7342     /* [HGM] now we promote for Shogi, if needed */
7343     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7344         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7345   }
7346
7347     if (gameInfo.holdingsWidth != 0) {
7348
7349       /* !!A lot more code needs to be written to support holdings  */
7350       /* [HGM] OK, so I have written it. Holdings are stored in the */
7351       /* penultimate board files, so they are automaticlly stored   */
7352       /* in the game history.                                       */
7353       if (fromY == DROP_RANK) {
7354         /* Delete from holdings, by decreasing count */
7355         /* and erasing image if necessary            */
7356         p = (int) fromX;
7357         if(p < (int) BlackPawn) { /* white drop */
7358              p -= (int)WhitePawn;
7359              if(p >= gameInfo.holdingsSize) p = 0;
7360              if(--board[p][BOARD_WIDTH-2] == 0)
7361                   board[p][BOARD_WIDTH-1] = EmptySquare;
7362         } else {                  /* black drop */
7363              p -= (int)BlackPawn;
7364              if(p >= gameInfo.holdingsSize) p = 0;
7365              if(--board[BOARD_HEIGHT-1-p][1] == 0)
7366                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7367         }
7368       }
7369       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7370           && gameInfo.variant != VariantBughouse        ) {
7371         /* [HGM] holdings: Add to holdings, if holdings exist */
7372         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7373                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7374                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7375         }
7376         p = (int) captured;
7377         if (p >= (int) BlackPawn) {
7378           p -= (int)BlackPawn;
7379           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7380                   /* in Shogi restore piece to its original  first */
7381                   captured = (ChessSquare) (DEMOTED captured);
7382                   p = DEMOTED p;
7383           }
7384           p = PieceToNumber((ChessSquare)p);
7385           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7386           board[p][BOARD_WIDTH-2]++;
7387           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7388         } else {
7389           p -= (int)WhitePawn;
7390           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7391                   captured = (ChessSquare) (DEMOTED captured);
7392                   p = DEMOTED p;
7393           }
7394           p = PieceToNumber((ChessSquare)p);
7395           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7396           board[BOARD_HEIGHT-1-p][1]++;
7397           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7398         }
7399       }
7400
7401     } else if (gameInfo.variant == VariantAtomic) {
7402       if (captured != EmptySquare) {
7403         int y, x;
7404         for (y = toY-1; y <= toY+1; y++) {
7405           for (x = toX-1; x <= toX+1; x++) {
7406             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7407                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7408               board[y][x] = EmptySquare;
7409             }
7410           }
7411         }
7412         board[toY][toX] = EmptySquare;
7413       }
7414     }
7415     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7416         /* [HGM] Shogi promotions */
7417         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7418     }
7419
7420     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7421                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7422         // [HGM] superchess: take promotion piece out of holdings
7423         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7424         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7425             if(!--board[k][BOARD_WIDTH-2])
7426                 board[k][BOARD_WIDTH-1] = EmptySquare;
7427         } else {
7428             if(!--board[BOARD_HEIGHT-1-k][1])
7429                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7430         }
7431     }
7432
7433 }
7434
7435 /* Updates forwardMostMove */
7436 void
7437 MakeMove(fromX, fromY, toX, toY, promoChar)
7438      int fromX, fromY, toX, toY;
7439      int promoChar;
7440 {
7441 //    forwardMostMove++; // [HGM] bare: moved downstream
7442
7443     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7444         int timeLeft; static int lastLoadFlag=0; int king, piece;
7445         piece = boards[forwardMostMove][fromY][fromX];
7446         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7447         if(gameInfo.variant == VariantKnightmate)
7448             king += (int) WhiteUnicorn - (int) WhiteKing;
7449         if(forwardMostMove == 0) {
7450             if(blackPlaysFirst) 
7451                 fprintf(serverMoves, "%s;", second.tidy);
7452             fprintf(serverMoves, "%s;", first.tidy);
7453             if(!blackPlaysFirst) 
7454                 fprintf(serverMoves, "%s;", second.tidy);
7455         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7456         lastLoadFlag = loadFlag;
7457         // print base move
7458         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7459         // print castling suffix
7460         if( toY == fromY && piece == king ) {
7461             if(toX-fromX > 1)
7462                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7463             if(fromX-toX >1)
7464                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7465         }
7466         // e.p. suffix
7467         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7468              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7469              boards[forwardMostMove][toY][toX] == EmptySquare
7470              && fromX != toX )
7471                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7472         // promotion suffix
7473         if(promoChar != NULLCHAR)
7474                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7475         if(!loadFlag) {
7476             fprintf(serverMoves, "/%d/%d",
7477                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7478             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7479             else                      timeLeft = blackTimeRemaining/1000;
7480             fprintf(serverMoves, "/%d", timeLeft);
7481         }
7482         fflush(serverMoves);
7483     }
7484
7485     if (forwardMostMove+1 >= MAX_MOVES) {
7486       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7487                         0, 1);
7488       return;
7489     }
7490     SwitchClocks();
7491     timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
7492     timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
7493     if (commentList[forwardMostMove+1] != NULL) {
7494         free(commentList[forwardMostMove+1]);
7495         commentList[forwardMostMove+1] = NULL;
7496     }
7497     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7498     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7499     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7500                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7501     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7502     gameInfo.result = GameUnfinished;
7503     if (gameInfo.resultDetails != NULL) {
7504         free(gameInfo.resultDetails);
7505         gameInfo.resultDetails = NULL;
7506     }
7507     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7508                               moveList[forwardMostMove - 1]);
7509     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7510                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7511                              fromY, fromX, toY, toX, promoChar,
7512                              parseList[forwardMostMove - 1]);
7513     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7514                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7515                             castlingRights[forwardMostMove]) ) {
7516       case MT_NONE:
7517       case MT_STALEMATE:
7518       default:
7519         break;
7520       case MT_CHECK:
7521         if(gameInfo.variant != VariantShogi)
7522             strcat(parseList[forwardMostMove - 1], "+");
7523         break;
7524       case MT_CHECKMATE:
7525       case MT_STAINMATE:
7526         strcat(parseList[forwardMostMove - 1], "#");
7527         break;
7528     }
7529     if (appData.debugMode) {
7530         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7531     }
7532
7533 }
7534
7535 /* Updates currentMove if not pausing */
7536 void
7537 ShowMove(fromX, fromY, toX, toY)
7538 {
7539     int instant = (gameMode == PlayFromGameFile) ?
7540         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7541     if(appData.noGUI) return;
7542     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7543         if (!instant) {
7544             if (forwardMostMove == currentMove + 1) {
7545                 AnimateMove(boards[forwardMostMove - 1],
7546                             fromX, fromY, toX, toY);
7547             }
7548             if (appData.highlightLastMove) {
7549                 SetHighlights(fromX, fromY, toX, toY);
7550             }
7551         }
7552         currentMove = forwardMostMove;
7553     }
7554
7555     if (instant) return;
7556
7557     DisplayMove(currentMove - 1);
7558     DrawPosition(FALSE, boards[currentMove]);
7559     DisplayBothClocks();
7560     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7561 }
7562
7563 void SendEgtPath(ChessProgramState *cps)
7564 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7565         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7566
7567         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7568
7569         while(*p) {
7570             char c, *q = name+1, *r, *s;
7571
7572             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7573             while(*p && *p != ',') *q++ = *p++;
7574             *q++ = ':'; *q = 0;
7575             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7576                 strcmp(name, ",nalimov:") == 0 ) {
7577                 // take nalimov path from the menu-changeable option first, if it is defined
7578                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7579                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7580             } else
7581             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7582                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7583                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7584                 s = r = StrStr(s, ":") + 1; // beginning of path info
7585                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7586                 c = *r; *r = 0;             // temporarily null-terminate path info
7587                     *--q = 0;               // strip of trailig ':' from name
7588                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7589                 *r = c;
7590                 SendToProgram(buf,cps);     // send egtbpath command for this format
7591             }
7592             if(*p == ',') p++; // read away comma to position for next format name
7593         }
7594 }
7595
7596 void
7597 InitChessProgram(cps, setup)
7598      ChessProgramState *cps;
7599      int setup; /* [HGM] needed to setup FRC opening position */
7600 {
7601     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7602     if (appData.noChessProgram) return;
7603     hintRequested = FALSE;
7604     bookRequested = FALSE;
7605
7606     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7607     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7608     if(cps->memSize) { /* [HGM] memory */
7609         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7610         SendToProgram(buf, cps);
7611     }
7612     SendEgtPath(cps); /* [HGM] EGT */
7613     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7614         sprintf(buf, "cores %d\n", appData.smpCores);
7615         SendToProgram(buf, cps);
7616     }
7617
7618     SendToProgram(cps->initString, cps);
7619     if (gameInfo.variant != VariantNormal &&
7620         gameInfo.variant != VariantLoadable
7621         /* [HGM] also send variant if board size non-standard */
7622         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7623                                             ) {
7624       char *v = VariantName(gameInfo.variant);
7625       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7626         /* [HGM] in protocol 1 we have to assume all variants valid */
7627         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7628         DisplayFatalError(buf, 0, 1);
7629         return;
7630       }
7631
7632       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7633       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7634       if( gameInfo.variant == VariantXiangqi )
7635            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7636       if( gameInfo.variant == VariantShogi )
7637            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7638       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7639            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7640       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7641                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7642            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7643       if( gameInfo.variant == VariantCourier )
7644            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7645       if( gameInfo.variant == VariantSuper )
7646            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7647       if( gameInfo.variant == VariantGreat )
7648            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7649
7650       if(overruled) {
7651            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7652                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7653            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7654            if(StrStr(cps->variants, b) == NULL) { 
7655                // specific sized variant not known, check if general sizing allowed
7656                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7657                    if(StrStr(cps->variants, "boardsize") == NULL) {
7658                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7659                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7660                        DisplayFatalError(buf, 0, 1);
7661                        return;
7662                    }
7663                    /* [HGM] here we really should compare with the maximum supported board size */
7664                }
7665            }
7666       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7667       sprintf(buf, "variant %s\n", b);
7668       SendToProgram(buf, cps);
7669     }
7670     currentlyInitializedVariant = gameInfo.variant;
7671
7672     /* [HGM] send opening position in FRC to first engine */
7673     if(setup) {
7674           SendToProgram("force\n", cps);
7675           SendBoard(cps, 0);
7676           /* engine is now in force mode! Set flag to wake it up after first move. */
7677           setboardSpoiledMachineBlack = 1;
7678     }
7679
7680     if (cps->sendICS) {
7681       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7682       SendToProgram(buf, cps);
7683     }
7684     cps->maybeThinking = FALSE;
7685     cps->offeredDraw = 0;
7686     if (!appData.icsActive) {
7687         SendTimeControl(cps, movesPerSession, timeControl,
7688                         timeIncrement, appData.searchDepth,
7689                         searchTime);
7690     }
7691     if (appData.showThinking 
7692         // [HGM] thinking: four options require thinking output to be sent
7693         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7694                                 ) {
7695         SendToProgram("post\n", cps);
7696     }
7697     SendToProgram("hard\n", cps);
7698     if (!appData.ponderNextMove) {
7699         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7700            it without being sure what state we are in first.  "hard"
7701            is not a toggle, so that one is OK.
7702          */
7703         SendToProgram("easy\n", cps);
7704     }
7705     if (cps->usePing) {
7706       sprintf(buf, "ping %d\n", ++cps->lastPing);
7707       SendToProgram(buf, cps);
7708     }
7709     cps->initDone = TRUE;
7710 }   
7711
7712
7713 void
7714 StartChessProgram(cps)
7715      ChessProgramState *cps;
7716 {
7717     char buf[MSG_SIZ];
7718     int err;
7719
7720     if (appData.noChessProgram) return;
7721     cps->initDone = FALSE;
7722
7723     if (strcmp(cps->host, "localhost") == 0) {
7724         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7725     } else if (*appData.remoteShell == NULLCHAR) {
7726         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7727     } else {
7728         if (*appData.remoteUser == NULLCHAR) {
7729           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7730                     cps->program);
7731         } else {
7732           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7733                     cps->host, appData.remoteUser, cps->program);
7734         }
7735         err = StartChildProcess(buf, "", &cps->pr);
7736     }
7737     
7738     if (err != 0) {
7739         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7740         DisplayFatalError(buf, err, 1);
7741         cps->pr = NoProc;
7742         cps->isr = NULL;
7743         return;
7744     }
7745     
7746     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7747     if (cps->protocolVersion > 1) {
7748       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7749       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7750       cps->comboCnt = 0;  //                and values of combo boxes
7751       SendToProgram(buf, cps);
7752     } else {
7753       SendToProgram("xboard\n", cps);
7754     }
7755 }
7756
7757
7758 void
7759 TwoMachinesEventIfReady P((void))
7760 {
7761   if (first.lastPing != first.lastPong) {
7762     DisplayMessage("", _("Waiting for first chess program"));
7763     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7764     return;
7765   }
7766   if (second.lastPing != second.lastPong) {
7767     DisplayMessage("", _("Waiting for second chess program"));
7768     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7769     return;
7770   }
7771   ThawUI();
7772   TwoMachinesEvent();
7773 }
7774
7775 void
7776 NextMatchGame P((void))
7777 {
7778     int index; /* [HGM] autoinc: step lod index during match */
7779     Reset(FALSE, TRUE);
7780     if (*appData.loadGameFile != NULLCHAR) {
7781         index = appData.loadGameIndex;
7782         if(index < 0) { // [HGM] autoinc
7783             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7784             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7785         } 
7786         LoadGameFromFile(appData.loadGameFile,
7787                          index,
7788                          appData.loadGameFile, FALSE);
7789     } else if (*appData.loadPositionFile != NULLCHAR) {
7790         index = appData.loadPositionIndex;
7791         if(index < 0) { // [HGM] autoinc
7792             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7793             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7794         } 
7795         LoadPositionFromFile(appData.loadPositionFile,
7796                              index,
7797                              appData.loadPositionFile);
7798     }
7799     TwoMachinesEventIfReady();
7800 }
7801
7802 void UserAdjudicationEvent( int result )
7803 {
7804     ChessMove gameResult = GameIsDrawn;
7805
7806     if( result > 0 ) {
7807         gameResult = WhiteWins;
7808     }
7809     else if( result < 0 ) {
7810         gameResult = BlackWins;
7811     }
7812
7813     if( gameMode == TwoMachinesPlay ) {
7814         GameEnds( gameResult, "User adjudication", GE_XBOARD );
7815     }
7816 }
7817
7818
7819 // [HGM] save: calculate checksum of game to make games easily identifiable
7820 int StringCheckSum(char *s)
7821 {
7822         int i = 0;
7823         if(s==NULL) return 0;
7824         while(*s) i = i*259 + *s++;
7825         return i;
7826 }
7827
7828 int GameCheckSum()
7829 {
7830         int i, sum=0;
7831         for(i=backwardMostMove; i<forwardMostMove; i++) {
7832                 sum += pvInfoList[i].depth;
7833                 sum += StringCheckSum(parseList[i]);
7834                 sum += StringCheckSum(commentList[i]);
7835                 sum *= 261;
7836         }
7837         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7838         return sum + StringCheckSum(commentList[i]);
7839 } // end of save patch
7840
7841 void
7842 GameEnds(result, resultDetails, whosays)
7843      ChessMove result;
7844      char *resultDetails;
7845      int whosays;
7846 {
7847     GameMode nextGameMode;
7848     int isIcsGame;
7849     char buf[MSG_SIZ];
7850
7851     if(endingGame) return; /* [HGM] crash: forbid recursion */
7852     endingGame = 1;
7853
7854     if (appData.debugMode) {
7855       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7856               result, resultDetails ? resultDetails : "(null)", whosays);
7857     }
7858
7859     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7860         /* If we are playing on ICS, the server decides when the
7861            game is over, but the engine can offer to draw, claim 
7862            a draw, or resign. 
7863          */
7864 #if ZIPPY
7865         if (appData.zippyPlay && first.initDone) {
7866             if (result == GameIsDrawn) {
7867                 /* In case draw still needs to be claimed */
7868                 SendToICS(ics_prefix);
7869                 SendToICS("draw\n");
7870             } else if (StrCaseStr(resultDetails, "resign")) {
7871                 SendToICS(ics_prefix);
7872                 SendToICS("resign\n");
7873             }
7874         }
7875 #endif
7876         endingGame = 0; /* [HGM] crash */
7877         return;
7878     }
7879
7880     /* If we're loading the game from a file, stop */
7881     if (whosays == GE_FILE) {
7882       (void) StopLoadGameTimer();
7883       gameFileFP = NULL;
7884     }
7885
7886     /* Cancel draw offers */
7887     first.offeredDraw = second.offeredDraw = 0;
7888
7889     /* If this is an ICS game, only ICS can really say it's done;
7890        if not, anyone can. */
7891     isIcsGame = (gameMode == IcsPlayingWhite || 
7892                  gameMode == IcsPlayingBlack || 
7893                  gameMode == IcsObserving    || 
7894                  gameMode == IcsExamining);
7895
7896     if (!isIcsGame || whosays == GE_ICS) {
7897         /* OK -- not an ICS game, or ICS said it was done */
7898         StopClocks();
7899         if (!isIcsGame && !appData.noChessProgram) 
7900           SetUserThinkingEnables();
7901     
7902         /* [HGM] if a machine claims the game end we verify this claim */
7903         if(gameMode == TwoMachinesPlay && appData.testClaims) {
7904             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7905                 char claimer;
7906                 ChessMove trueResult = (ChessMove) -1;
7907
7908                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
7909                                             first.twoMachinesColor[0] :
7910                                             second.twoMachinesColor[0] ;
7911
7912                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7913                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7914                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7915                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7916                 } else
7917                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7918                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7919                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7920                 } else
7921                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7922                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7923                 }
7924
7925                 // now verify win claims, but not in drop games, as we don't understand those yet
7926                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7927                                                  || gameInfo.variant == VariantGreat) &&
7928                     (result == WhiteWins && claimer == 'w' ||
7929                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
7930                       if (appData.debugMode) {
7931                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
7932                                 result, epStatus[forwardMostMove], forwardMostMove);
7933                       }
7934                       if(result != trueResult) {
7935                               sprintf(buf, "False win claim: '%s'", resultDetails);
7936                               result = claimer == 'w' ? BlackWins : WhiteWins;
7937                               resultDetails = buf;
7938                       }
7939                 } else
7940                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7941                     && (forwardMostMove <= backwardMostMove ||
7942                         epStatus[forwardMostMove-1] > EP_DRAWS ||
7943                         (claimer=='b')==(forwardMostMove&1))
7944                                                                                   ) {
7945                       /* [HGM] verify: draws that were not flagged are false claims */
7946                       sprintf(buf, "False draw claim: '%s'", resultDetails);
7947                       result = claimer == 'w' ? BlackWins : WhiteWins;
7948                       resultDetails = buf;
7949                 }
7950                 /* (Claiming a loss is accepted no questions asked!) */
7951             }
7952             /* [HGM] bare: don't allow bare King to win */
7953             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7954                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
7955                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7956                && result != GameIsDrawn)
7957             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7958                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7959                         int p = (int)boards[forwardMostMove][i][j] - color;
7960                         if(p >= 0 && p <= (int)WhiteKing) k++;
7961                 }
7962                 if (appData.debugMode) {
7963                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7964                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7965                 }
7966                 if(k <= 1) {
7967                         result = GameIsDrawn;
7968                         sprintf(buf, "%s but bare king", resultDetails);
7969                         resultDetails = buf;
7970                 }
7971             }
7972         }
7973
7974
7975         if(serverMoves != NULL && !loadFlag) { char c = '=';
7976             if(result==WhiteWins) c = '+';
7977             if(result==BlackWins) c = '-';
7978             if(resultDetails != NULL)
7979                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7980         }
7981         if (resultDetails != NULL) {
7982             gameInfo.result = result;
7983             gameInfo.resultDetails = StrSave(resultDetails);
7984
7985             /* display last move only if game was not loaded from file */
7986             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7987                 DisplayMove(currentMove - 1);
7988     
7989             if (forwardMostMove != 0) {
7990                 if (gameMode != PlayFromGameFile && gameMode != EditGame
7991                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7992                                                                 ) {
7993                     if (*appData.saveGameFile != NULLCHAR) {
7994                         SaveGameToFile(appData.saveGameFile, TRUE);
7995                     } else if (appData.autoSaveGames) {
7996                         AutoSaveGame();
7997                     }
7998                     if (*appData.savePositionFile != NULLCHAR) {
7999                         SavePositionToFile(appData.savePositionFile);
8000                     }
8001                 }
8002             }
8003
8004             /* Tell program how game ended in case it is learning */
8005             /* [HGM] Moved this to after saving the PGN, just in case */
8006             /* engine died and we got here through time loss. In that */
8007             /* case we will get a fatal error writing the pipe, which */
8008             /* would otherwise lose us the PGN.                       */
8009             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8010             /* output during GameEnds should never be fatal anymore   */
8011             if (gameMode == MachinePlaysWhite ||
8012                 gameMode == MachinePlaysBlack ||
8013                 gameMode == TwoMachinesPlay ||
8014                 gameMode == IcsPlayingWhite ||
8015                 gameMode == IcsPlayingBlack ||
8016                 gameMode == BeginningOfGame) {
8017                 char buf[MSG_SIZ];
8018                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8019                         resultDetails);
8020                 if (first.pr != NoProc) {
8021                     SendToProgram(buf, &first);
8022                 }
8023                 if (second.pr != NoProc &&
8024                     gameMode == TwoMachinesPlay) {
8025                     SendToProgram(buf, &second);
8026                 }
8027             }
8028         }
8029
8030         if (appData.icsActive) {
8031             if (appData.quietPlay &&
8032                 (gameMode == IcsPlayingWhite ||
8033                  gameMode == IcsPlayingBlack)) {
8034                 SendToICS(ics_prefix);
8035                 SendToICS("set shout 1\n");
8036             }
8037             nextGameMode = IcsIdle;
8038             ics_user_moved = FALSE;
8039             /* clean up premove.  It's ugly when the game has ended and the
8040              * premove highlights are still on the board.
8041              */
8042             if (gotPremove) {
8043               gotPremove = FALSE;
8044               ClearPremoveHighlights();
8045               DrawPosition(FALSE, boards[currentMove]);
8046             }
8047             if (whosays == GE_ICS) {
8048                 switch (result) {
8049                 case WhiteWins:
8050                     if (gameMode == IcsPlayingWhite)
8051                         PlayIcsWinSound();
8052                     else if(gameMode == IcsPlayingBlack)
8053                         PlayIcsLossSound();
8054                     break;
8055                 case BlackWins:
8056                     if (gameMode == IcsPlayingBlack)
8057                         PlayIcsWinSound();
8058                     else if(gameMode == IcsPlayingWhite)
8059                         PlayIcsLossSound();
8060                     break;
8061                 case GameIsDrawn:
8062                     PlayIcsDrawSound();
8063                     break;
8064                 default:
8065                     PlayIcsUnfinishedSound();
8066                 }
8067             }
8068         } else if (gameMode == EditGame ||
8069                    gameMode == PlayFromGameFile || 
8070                    gameMode == AnalyzeMode || 
8071                    gameMode == AnalyzeFile) {
8072             nextGameMode = gameMode;
8073         } else {
8074             nextGameMode = EndOfGame;
8075         }
8076         pausing = FALSE;
8077         ModeHighlight();
8078     } else {
8079         nextGameMode = gameMode;
8080     }
8081
8082     if (appData.noChessProgram) {
8083         gameMode = nextGameMode;
8084         ModeHighlight();
8085         endingGame = 0; /* [HGM] crash */
8086         return;
8087     }
8088
8089     if (first.reuse) {
8090         /* Put first chess program into idle state */
8091         if (first.pr != NoProc &&
8092             (gameMode == MachinePlaysWhite ||
8093              gameMode == MachinePlaysBlack ||
8094              gameMode == TwoMachinesPlay ||
8095              gameMode == IcsPlayingWhite ||
8096              gameMode == IcsPlayingBlack ||
8097              gameMode == BeginningOfGame)) {
8098             SendToProgram("force\n", &first);
8099             if (first.usePing) {
8100               char buf[MSG_SIZ];
8101               sprintf(buf, "ping %d\n", ++first.lastPing);
8102               SendToProgram(buf, &first);
8103             }
8104         }
8105     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8106         /* Kill off first chess program */
8107         if (first.isr != NULL)
8108           RemoveInputSource(first.isr);
8109         first.isr = NULL;
8110     
8111         if (first.pr != NoProc) {
8112             ExitAnalyzeMode();
8113             DoSleep( appData.delayBeforeQuit );
8114             SendToProgram("quit\n", &first);
8115             DoSleep( appData.delayAfterQuit );
8116             DestroyChildProcess(first.pr, first.useSigterm);
8117         }
8118         first.pr = NoProc;
8119     }
8120     if (second.reuse) {
8121         /* Put second chess program into idle state */
8122         if (second.pr != NoProc &&
8123             gameMode == TwoMachinesPlay) {
8124             SendToProgram("force\n", &second);
8125             if (second.usePing) {
8126               char buf[MSG_SIZ];
8127               sprintf(buf, "ping %d\n", ++second.lastPing);
8128               SendToProgram(buf, &second);
8129             }
8130         }
8131     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8132         /* Kill off second chess program */
8133         if (second.isr != NULL)
8134           RemoveInputSource(second.isr);
8135         second.isr = NULL;
8136     
8137         if (second.pr != NoProc) {
8138             DoSleep( appData.delayBeforeQuit );
8139             SendToProgram("quit\n", &second);
8140             DoSleep( appData.delayAfterQuit );
8141             DestroyChildProcess(second.pr, second.useSigterm);
8142         }
8143         second.pr = NoProc;
8144     }
8145
8146     if (matchMode && gameMode == TwoMachinesPlay) {
8147         switch (result) {
8148         case WhiteWins:
8149           if (first.twoMachinesColor[0] == 'w') {
8150             first.matchWins++;
8151           } else {
8152             second.matchWins++;
8153           }
8154           break;
8155         case BlackWins:
8156           if (first.twoMachinesColor[0] == 'b') {
8157             first.matchWins++;
8158           } else {
8159             second.matchWins++;
8160           }
8161           break;
8162         default:
8163           break;
8164         }
8165         if (matchGame < appData.matchGames) {
8166             char *tmp;
8167             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8168                 tmp = first.twoMachinesColor;
8169                 first.twoMachinesColor = second.twoMachinesColor;
8170                 second.twoMachinesColor = tmp;
8171             }
8172             gameMode = nextGameMode;
8173             matchGame++;
8174             if(appData.matchPause>10000 || appData.matchPause<10)
8175                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8176             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8177             endingGame = 0; /* [HGM] crash */
8178             return;
8179         } else {
8180             char buf[MSG_SIZ];
8181             gameMode = nextGameMode;
8182             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8183                     first.tidy, second.tidy,
8184                     first.matchWins, second.matchWins,
8185                     appData.matchGames - (first.matchWins + second.matchWins));
8186             DisplayFatalError(buf, 0, 0);
8187         }
8188     }
8189     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8190         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8191       ExitAnalyzeMode();
8192     gameMode = nextGameMode;
8193     ModeHighlight();
8194     endingGame = 0;  /* [HGM] crash */
8195 }
8196
8197 /* Assumes program was just initialized (initString sent).
8198    Leaves program in force mode. */
8199 void
8200 FeedMovesToProgram(cps, upto) 
8201      ChessProgramState *cps;
8202      int upto;
8203 {
8204     int i;
8205     
8206     if (appData.debugMode)
8207       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8208               startedFromSetupPosition ? "position and " : "",
8209               backwardMostMove, upto, cps->which);
8210     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8211         // [HGM] variantswitch: make engine aware of new variant
8212         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8213                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8214         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8215         SendToProgram(buf, cps);
8216         currentlyInitializedVariant = gameInfo.variant;
8217     }
8218     SendToProgram("force\n", cps);
8219     if (startedFromSetupPosition) {
8220         SendBoard(cps, backwardMostMove);
8221     if (appData.debugMode) {
8222         fprintf(debugFP, "feedMoves\n");
8223     }
8224     }
8225     for (i = backwardMostMove; i < upto; i++) {
8226         SendMoveToProgram(i, cps);
8227     }
8228 }
8229
8230
8231 void
8232 ResurrectChessProgram()
8233 {
8234      /* The chess program may have exited.
8235         If so, restart it and feed it all the moves made so far. */
8236
8237     if (appData.noChessProgram || first.pr != NoProc) return;
8238     
8239     StartChessProgram(&first);
8240     InitChessProgram(&first, FALSE);
8241     FeedMovesToProgram(&first, currentMove);
8242
8243     if (!first.sendTime) {
8244         /* can't tell gnuchess what its clock should read,
8245            so we bow to its notion. */
8246         ResetClocks();
8247         timeRemaining[0][currentMove] = whiteTimeRemaining;
8248         timeRemaining[1][currentMove] = blackTimeRemaining;
8249     }
8250
8251     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8252                 appData.icsEngineAnalyze) && first.analysisSupport) {
8253       SendToProgram("analyze\n", &first);
8254       first.analyzing = TRUE;
8255     }
8256 }
8257
8258 /*
8259  * Button procedures
8260  */
8261 void
8262 Reset(redraw, init)
8263      int redraw, init;
8264 {
8265     int i;
8266
8267     if (appData.debugMode) {
8268         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8269                 redraw, init, gameMode);
8270     }
8271     pausing = pauseExamInvalid = FALSE;
8272     startedFromSetupPosition = blackPlaysFirst = FALSE;
8273     firstMove = TRUE;
8274     whiteFlag = blackFlag = FALSE;
8275     userOfferedDraw = FALSE;
8276     hintRequested = bookRequested = FALSE;
8277     first.maybeThinking = FALSE;
8278     second.maybeThinking = FALSE;
8279     first.bookSuspend = FALSE; // [HGM] book
8280     second.bookSuspend = FALSE;
8281     thinkOutput[0] = NULLCHAR;
8282     lastHint[0] = NULLCHAR;
8283     ClearGameInfo(&gameInfo);
8284     gameInfo.variant = StringToVariant(appData.variant);
8285     ics_user_moved = ics_clock_paused = FALSE;
8286     ics_getting_history = H_FALSE;
8287     ics_gamenum = -1;
8288     white_holding[0] = black_holding[0] = NULLCHAR;
8289     ClearProgramStats();
8290     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8291     
8292     ResetFrontEnd();
8293     ClearHighlights();
8294     flipView = appData.flipView;
8295     ClearPremoveHighlights();
8296     gotPremove = FALSE;
8297     alarmSounded = FALSE;
8298
8299     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8300     if(appData.serverMovesName != NULL) {
8301         /* [HGM] prepare to make moves file for broadcasting */
8302         clock_t t = clock();
8303         if(serverMoves != NULL) fclose(serverMoves);
8304         serverMoves = fopen(appData.serverMovesName, "r");
8305         if(serverMoves != NULL) {
8306             fclose(serverMoves);
8307             /* delay 15 sec before overwriting, so all clients can see end */
8308             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8309         }
8310         serverMoves = fopen(appData.serverMovesName, "w");
8311     }
8312
8313     ExitAnalyzeMode();
8314     gameMode = BeginningOfGame;
8315     ModeHighlight();
8316     if(appData.icsActive) gameInfo.variant = VariantNormal;
8317     currentMove = forwardMostMove = backwardMostMove = 0;
8318     InitPosition(redraw);
8319     for (i = 0; i < MAX_MOVES; i++) {
8320         if (commentList[i] != NULL) {
8321             free(commentList[i]);
8322             commentList[i] = NULL;
8323         }
8324     }
8325     ResetClocks();
8326     timeRemaining[0][0] = whiteTimeRemaining;
8327     timeRemaining[1][0] = blackTimeRemaining;
8328     if (first.pr == NULL) {
8329         StartChessProgram(&first);
8330     }
8331     if (init) {
8332             InitChessProgram(&first, startedFromSetupPosition);
8333     }
8334     DisplayTitle("");
8335     DisplayMessage("", "");
8336     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8337     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8338 }
8339
8340 void
8341 AutoPlayGameLoop()
8342 {
8343     for (;;) {
8344         if (!AutoPlayOneMove())
8345           return;
8346         if (matchMode || appData.timeDelay == 0)
8347           continue;
8348         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8349           return;
8350         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8351         break;
8352     }
8353 }
8354
8355
8356 int
8357 AutoPlayOneMove()
8358 {
8359     int fromX, fromY, toX, toY;
8360
8361     if (appData.debugMode) {
8362       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8363     }
8364
8365     if (gameMode != PlayFromGameFile)
8366       return FALSE;
8367
8368     if (currentMove >= forwardMostMove) {
8369       gameMode = EditGame;
8370       ModeHighlight();
8371
8372       /* [AS] Clear current move marker at the end of a game */
8373       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8374
8375       return FALSE;
8376     }
8377     
8378     toX = moveList[currentMove][2] - AAA;
8379     toY = moveList[currentMove][3] - ONE;
8380
8381     if (moveList[currentMove][1] == '@') {
8382         if (appData.highlightLastMove) {
8383             SetHighlights(-1, -1, toX, toY);
8384         }
8385     } else {
8386         fromX = moveList[currentMove][0] - AAA;
8387         fromY = moveList[currentMove][1] - ONE;
8388
8389         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8390
8391         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8392
8393         if (appData.highlightLastMove) {
8394             SetHighlights(fromX, fromY, toX, toY);
8395         }
8396     }
8397     DisplayMove(currentMove);
8398     SendMoveToProgram(currentMove++, &first);
8399     DisplayBothClocks();
8400     DrawPosition(FALSE, boards[currentMove]);
8401     // [HGM] PV info: always display, routine tests if empty
8402     DisplayComment(currentMove - 1, commentList[currentMove]);
8403     return TRUE;
8404 }
8405
8406
8407 int
8408 LoadGameOneMove(readAhead)
8409      ChessMove readAhead;
8410 {
8411     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8412     char promoChar = NULLCHAR;
8413     ChessMove moveType;
8414     char move[MSG_SIZ];
8415     char *p, *q;
8416     
8417     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8418         gameMode != AnalyzeMode && gameMode != Training) {
8419         gameFileFP = NULL;
8420         return FALSE;
8421     }
8422     
8423     yyboardindex = forwardMostMove;
8424     if (readAhead != (ChessMove)0) {
8425       moveType = readAhead;
8426     } else {
8427       if (gameFileFP == NULL)
8428           return FALSE;
8429       moveType = (ChessMove) yylex();
8430     }
8431     
8432     done = FALSE;
8433     switch (moveType) {
8434       case Comment:
8435         if (appData.debugMode) 
8436           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8437         p = yy_text;
8438         if (*p == '{' || *p == '[' || *p == '(') {
8439             p[strlen(p) - 1] = NULLCHAR;
8440             p++;
8441         }
8442
8443         /* append the comment but don't display it */
8444         while (*p == '\n') p++;
8445         AppendComment(currentMove, p);
8446         return TRUE;
8447
8448       case WhiteCapturesEnPassant:
8449       case BlackCapturesEnPassant:
8450       case WhitePromotionChancellor:
8451       case BlackPromotionChancellor:
8452       case WhitePromotionArchbishop:
8453       case BlackPromotionArchbishop:
8454       case WhitePromotionCentaur:
8455       case BlackPromotionCentaur:
8456       case WhitePromotionQueen:
8457       case BlackPromotionQueen:
8458       case WhitePromotionRook:
8459       case BlackPromotionRook:
8460       case WhitePromotionBishop:
8461       case BlackPromotionBishop:
8462       case WhitePromotionKnight:
8463       case BlackPromotionKnight:
8464       case WhitePromotionKing:
8465       case BlackPromotionKing:
8466       case NormalMove:
8467       case WhiteKingSideCastle:
8468       case WhiteQueenSideCastle:
8469       case BlackKingSideCastle:
8470       case BlackQueenSideCastle:
8471       case WhiteKingSideCastleWild:
8472       case WhiteQueenSideCastleWild:
8473       case BlackKingSideCastleWild:
8474       case BlackQueenSideCastleWild:
8475       /* PUSH Fabien */
8476       case WhiteHSideCastleFR:
8477       case WhiteASideCastleFR:
8478       case BlackHSideCastleFR:
8479       case BlackASideCastleFR:
8480       /* POP Fabien */
8481         if (appData.debugMode)
8482           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8483         fromX = currentMoveString[0] - AAA;
8484         fromY = currentMoveString[1] - ONE;
8485         toX = currentMoveString[2] - AAA;
8486         toY = currentMoveString[3] - ONE;
8487         promoChar = currentMoveString[4];
8488         break;
8489
8490       case WhiteDrop:
8491       case BlackDrop:
8492         if (appData.debugMode)
8493           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8494         fromX = moveType == WhiteDrop ?
8495           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8496         (int) CharToPiece(ToLower(currentMoveString[0]));
8497         fromY = DROP_RANK;
8498         toX = currentMoveString[2] - AAA;
8499         toY = currentMoveString[3] - ONE;
8500         break;
8501
8502       case WhiteWins:
8503       case BlackWins:
8504       case GameIsDrawn:
8505       case GameUnfinished:
8506         if (appData.debugMode)
8507           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8508         p = strchr(yy_text, '{');
8509         if (p == NULL) p = strchr(yy_text, '(');
8510         if (p == NULL) {
8511             p = yy_text;
8512             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8513         } else {
8514             q = strchr(p, *p == '{' ? '}' : ')');
8515             if (q != NULL) *q = NULLCHAR;
8516             p++;
8517         }
8518         GameEnds(moveType, p, GE_FILE);
8519         done = TRUE;
8520         if (cmailMsgLoaded) {
8521             ClearHighlights();
8522             flipView = WhiteOnMove(currentMove);
8523             if (moveType == GameUnfinished) flipView = !flipView;
8524             if (appData.debugMode)
8525               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8526         }
8527         break;
8528
8529       case (ChessMove) 0:       /* end of file */
8530         if (appData.debugMode)
8531           fprintf(debugFP, "Parser hit end of file\n");
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 MoveNumberOne:
8553         if (lastLoadGameStart == GNUChessGame) {
8554             /* GNUChessGames have numbers, but they aren't move numbers */
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         /* else fall thru */
8561
8562       case XBoardGame:
8563       case GNUChessGame:
8564       case PGNTag:
8565         /* Reached start of next game in file */
8566         if (appData.debugMode)
8567           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8568         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8569                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8570           case MT_NONE:
8571           case MT_CHECK:
8572             break;
8573           case MT_CHECKMATE:
8574           case MT_STAINMATE:
8575             if (WhiteOnMove(currentMove)) {
8576                 GameEnds(BlackWins, "Black mates", GE_FILE);
8577             } else {
8578                 GameEnds(WhiteWins, "White mates", GE_FILE);
8579             }
8580             break;
8581           case MT_STALEMATE:
8582             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8583             break;
8584         }
8585         done = TRUE;
8586         break;
8587
8588       case PositionDiagram:     /* should not happen; ignore */
8589       case ElapsedTime:         /* ignore */
8590       case NAG:                 /* ignore */
8591         if (appData.debugMode)
8592           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8593                   yy_text, (int) moveType);
8594         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8595
8596       case IllegalMove:
8597         if (appData.testLegality) {
8598             if (appData.debugMode)
8599               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8600             sprintf(move, _("Illegal move: %d.%s%s"),
8601                     (forwardMostMove / 2) + 1,
8602                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8603             DisplayError(move, 0);
8604             done = TRUE;
8605         } else {
8606             if (appData.debugMode)
8607               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8608                       yy_text, currentMoveString);
8609             fromX = currentMoveString[0] - AAA;
8610             fromY = currentMoveString[1] - ONE;
8611             toX = currentMoveString[2] - AAA;
8612             toY = currentMoveString[3] - ONE;
8613             promoChar = currentMoveString[4];
8614         }
8615         break;
8616
8617       case AmbiguousMove:
8618         if (appData.debugMode)
8619           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8620         sprintf(move, _("Ambiguous move: %d.%s%s"),
8621                 (forwardMostMove / 2) + 1,
8622                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8623         DisplayError(move, 0);
8624         done = TRUE;
8625         break;
8626
8627       default:
8628       case ImpossibleMove:
8629         if (appData.debugMode)
8630           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8631         sprintf(move, _("Illegal move: %d.%s%s"),
8632                 (forwardMostMove / 2) + 1,
8633                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8634         DisplayError(move, 0);
8635         done = TRUE;
8636         break;
8637     }
8638
8639     if (done) {
8640         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8641             DrawPosition(FALSE, boards[currentMove]);
8642             DisplayBothClocks();
8643             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8644               DisplayComment(currentMove - 1, commentList[currentMove]);
8645         }
8646         (void) StopLoadGameTimer();
8647         gameFileFP = NULL;
8648         cmailOldMove = forwardMostMove;
8649         return FALSE;
8650     } else {
8651         /* currentMoveString is set as a side-effect of yylex */
8652         strcat(currentMoveString, "\n");
8653         strcpy(moveList[forwardMostMove], currentMoveString);
8654         
8655         thinkOutput[0] = NULLCHAR;
8656         MakeMove(fromX, fromY, toX, toY, promoChar);
8657         currentMove = forwardMostMove;
8658         return TRUE;
8659     }
8660 }
8661
8662 /* Load the nth game from the given file */
8663 int
8664 LoadGameFromFile(filename, n, title, useList)
8665      char *filename;
8666      int n;
8667      char *title;
8668      /*Boolean*/ int useList;
8669 {
8670     FILE *f;
8671     char buf[MSG_SIZ];
8672
8673     if (strcmp(filename, "-") == 0) {
8674         f = stdin;
8675         title = "stdin";
8676     } else {
8677         f = fopen(filename, "rb");
8678         if (f == NULL) {
8679           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8680             DisplayError(buf, errno);
8681             return FALSE;
8682         }
8683     }
8684     if (fseek(f, 0, 0) == -1) {
8685         /* f is not seekable; probably a pipe */
8686         useList = FALSE;
8687     }
8688     if (useList && n == 0) {
8689         int error = GameListBuild(f);
8690         if (error) {
8691             DisplayError(_("Cannot build game list"), error);
8692         } else if (!ListEmpty(&gameList) &&
8693                    ((ListGame *) gameList.tailPred)->number > 1) {
8694             GameListPopUp(f, title);
8695             return TRUE;
8696         }
8697         GameListDestroy();
8698         n = 1;
8699     }
8700     if (n == 0) n = 1;
8701     return LoadGame(f, n, title, FALSE);
8702 }
8703
8704
8705 void
8706 MakeRegisteredMove()
8707 {
8708     int fromX, fromY, toX, toY;
8709     char promoChar;
8710     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8711         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8712           case CMAIL_MOVE:
8713           case CMAIL_DRAW:
8714             if (appData.debugMode)
8715               fprintf(debugFP, "Restoring %s for game %d\n",
8716                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8717     
8718             thinkOutput[0] = NULLCHAR;
8719             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8720             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8721             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8722             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8723             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8724             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8725             MakeMove(fromX, fromY, toX, toY, promoChar);
8726             ShowMove(fromX, fromY, toX, toY);
8727               
8728             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8729                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8730               case MT_NONE:
8731               case MT_CHECK:
8732                 break;
8733                 
8734               case MT_CHECKMATE:
8735               case MT_STAINMATE:
8736                 if (WhiteOnMove(currentMove)) {
8737                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8738                 } else {
8739                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8740                 }
8741                 break;
8742                 
8743               case MT_STALEMATE:
8744                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8745                 break;
8746             }
8747
8748             break;
8749             
8750           case CMAIL_RESIGN:
8751             if (WhiteOnMove(currentMove)) {
8752                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8753             } else {
8754                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8755             }
8756             break;
8757             
8758           case CMAIL_ACCEPT:
8759             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8760             break;
8761               
8762           default:
8763             break;
8764         }
8765     }
8766
8767     return;
8768 }
8769
8770 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8771 int
8772 CmailLoadGame(f, gameNumber, title, useList)
8773      FILE *f;
8774      int gameNumber;
8775      char *title;
8776      int useList;
8777 {
8778     int retVal;
8779
8780     if (gameNumber > nCmailGames) {
8781         DisplayError(_("No more games in this message"), 0);
8782         return FALSE;
8783     }
8784     if (f == lastLoadGameFP) {
8785         int offset = gameNumber - lastLoadGameNumber;
8786         if (offset == 0) {
8787             cmailMsg[0] = NULLCHAR;
8788             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8789                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8790                 nCmailMovesRegistered--;
8791             }
8792             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8793             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8794                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8795             }
8796         } else {
8797             if (! RegisterMove()) return FALSE;
8798         }
8799     }
8800
8801     retVal = LoadGame(f, gameNumber, title, useList);
8802
8803     /* Make move registered during previous look at this game, if any */
8804     MakeRegisteredMove();
8805
8806     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8807         commentList[currentMove]
8808           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8809         DisplayComment(currentMove - 1, commentList[currentMove]);
8810     }
8811
8812     return retVal;
8813 }
8814
8815 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8816 int
8817 ReloadGame(offset)
8818      int offset;
8819 {
8820     int gameNumber = lastLoadGameNumber + offset;
8821     if (lastLoadGameFP == NULL) {
8822         DisplayError(_("No game has been loaded yet"), 0);
8823         return FALSE;
8824     }
8825     if (gameNumber <= 0) {
8826         DisplayError(_("Can't back up any further"), 0);
8827         return FALSE;
8828     }
8829     if (cmailMsgLoaded) {
8830         return CmailLoadGame(lastLoadGameFP, gameNumber,
8831                              lastLoadGameTitle, lastLoadGameUseList);
8832     } else {
8833         return LoadGame(lastLoadGameFP, gameNumber,
8834                         lastLoadGameTitle, lastLoadGameUseList);
8835     }
8836 }
8837
8838
8839
8840 /* Load the nth game from open file f */
8841 int
8842 LoadGame(f, gameNumber, title, useList)
8843      FILE *f;
8844      int gameNumber;
8845      char *title;
8846      int useList;
8847 {
8848     ChessMove cm;
8849     char buf[MSG_SIZ];
8850     int gn = gameNumber;
8851     ListGame *lg = NULL;
8852     int numPGNTags = 0;
8853     int err;
8854     GameMode oldGameMode;
8855     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8856
8857     if (appData.debugMode) 
8858         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8859
8860     if (gameMode == Training )
8861         SetTrainingModeOff();
8862
8863     oldGameMode = gameMode;
8864     if (gameMode != BeginningOfGame) {
8865       Reset(FALSE, TRUE);
8866     }
8867
8868     gameFileFP = f;
8869     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8870         fclose(lastLoadGameFP);
8871     }
8872
8873     if (useList) {
8874         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8875         
8876         if (lg) {
8877             fseek(f, lg->offset, 0);
8878             GameListHighlight(gameNumber);
8879             gn = 1;
8880         }
8881         else {
8882             DisplayError(_("Game number out of range"), 0);
8883             return FALSE;
8884         }
8885     } else {
8886         GameListDestroy();
8887         if (fseek(f, 0, 0) == -1) {
8888             if (f == lastLoadGameFP ?
8889                 gameNumber == lastLoadGameNumber + 1 :
8890                 gameNumber == 1) {
8891                 gn = 1;
8892             } else {
8893                 DisplayError(_("Can't seek on game file"), 0);
8894                 return FALSE;
8895             }
8896         }
8897     }
8898     lastLoadGameFP = f;
8899     lastLoadGameNumber = gameNumber;
8900     strcpy(lastLoadGameTitle, title);
8901     lastLoadGameUseList = useList;
8902
8903     yynewfile(f);
8904
8905     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8906       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8907                 lg->gameInfo.black);
8908             DisplayTitle(buf);
8909     } else if (*title != NULLCHAR) {
8910         if (gameNumber > 1) {
8911             sprintf(buf, "%s %d", title, gameNumber);
8912             DisplayTitle(buf);
8913         } else {
8914             DisplayTitle(title);
8915         }
8916     }
8917
8918     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8919         gameMode = PlayFromGameFile;
8920         ModeHighlight();
8921     }
8922
8923     currentMove = forwardMostMove = backwardMostMove = 0;
8924     CopyBoard(boards[0], initialPosition);
8925     StopClocks();
8926
8927     /*
8928      * Skip the first gn-1 games in the file.
8929      * Also skip over anything that precedes an identifiable 
8930      * start of game marker, to avoid being confused by 
8931      * garbage at the start of the file.  Currently 
8932      * recognized start of game markers are the move number "1",
8933      * the pattern "gnuchess .* game", the pattern
8934      * "^[#;%] [^ ]* game file", and a PGN tag block.  
8935      * A game that starts with one of the latter two patterns
8936      * will also have a move number 1, possibly
8937      * following a position diagram.
8938      * 5-4-02: Let's try being more lenient and allowing a game to
8939      * start with an unnumbered move.  Does that break anything?
8940      */
8941     cm = lastLoadGameStart = (ChessMove) 0;
8942     while (gn > 0) {
8943         yyboardindex = forwardMostMove;
8944         cm = (ChessMove) yylex();
8945         switch (cm) {
8946           case (ChessMove) 0:
8947             if (cmailMsgLoaded) {
8948                 nCmailGames = CMAIL_MAX_GAMES - gn;
8949             } else {
8950                 Reset(TRUE, TRUE);
8951                 DisplayError(_("Game not found in file"), 0);
8952             }
8953             return FALSE;
8954
8955           case GNUChessGame:
8956           case XBoardGame:
8957             gn--;
8958             lastLoadGameStart = cm;
8959             break;
8960             
8961           case MoveNumberOne:
8962             switch (lastLoadGameStart) {
8963               case GNUChessGame:
8964               case XBoardGame:
8965               case PGNTag:
8966                 break;
8967               case MoveNumberOne:
8968               case (ChessMove) 0:
8969                 gn--;           /* count this game */
8970                 lastLoadGameStart = cm;
8971                 break;
8972               default:
8973                 /* impossible */
8974                 break;
8975             }
8976             break;
8977
8978           case PGNTag:
8979             switch (lastLoadGameStart) {
8980               case GNUChessGame:
8981               case PGNTag:
8982               case MoveNumberOne:
8983               case (ChessMove) 0:
8984                 gn--;           /* count this game */
8985                 lastLoadGameStart = cm;
8986                 break;
8987               case XBoardGame:
8988                 lastLoadGameStart = cm; /* game counted already */
8989                 break;
8990               default:
8991                 /* impossible */
8992                 break;
8993             }
8994             if (gn > 0) {
8995                 do {
8996                     yyboardindex = forwardMostMove;
8997                     cm = (ChessMove) yylex();
8998                 } while (cm == PGNTag || cm == Comment);
8999             }
9000             break;
9001
9002           case WhiteWins:
9003           case BlackWins:
9004           case GameIsDrawn:
9005             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9006                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9007                     != CMAIL_OLD_RESULT) {
9008                     nCmailResults ++ ;
9009                     cmailResult[  CMAIL_MAX_GAMES
9010                                 - gn - 1] = CMAIL_OLD_RESULT;
9011                 }
9012             }
9013             break;
9014
9015           case NormalMove:
9016             /* Only a NormalMove can be at the start of a game
9017              * without a position diagram. */
9018             if (lastLoadGameStart == (ChessMove) 0) {
9019               gn--;
9020               lastLoadGameStart = MoveNumberOne;
9021             }
9022             break;
9023
9024           default:
9025             break;
9026         }
9027     }
9028     
9029     if (appData.debugMode)
9030       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9031
9032     if (cm == XBoardGame) {
9033         /* Skip any header junk before position diagram and/or move 1 */
9034         for (;;) {
9035             yyboardindex = forwardMostMove;
9036             cm = (ChessMove) yylex();
9037
9038             if (cm == (ChessMove) 0 ||
9039                 cm == GNUChessGame || cm == XBoardGame) {
9040                 /* Empty game; pretend end-of-file and handle later */
9041                 cm = (ChessMove) 0;
9042                 break;
9043             }
9044
9045             if (cm == MoveNumberOne || cm == PositionDiagram ||
9046                 cm == PGNTag || cm == Comment)
9047               break;
9048         }
9049     } else if (cm == GNUChessGame) {
9050         if (gameInfo.event != NULL) {
9051             free(gameInfo.event);
9052         }
9053         gameInfo.event = StrSave(yy_text);
9054     }   
9055
9056     startedFromSetupPosition = FALSE;
9057     while (cm == PGNTag) {
9058         if (appData.debugMode) 
9059           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9060         err = ParsePGNTag(yy_text, &gameInfo);
9061         if (!err) numPGNTags++;
9062
9063         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9064         if(gameInfo.variant != oldVariant) {
9065             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9066             InitPosition(TRUE);
9067             oldVariant = gameInfo.variant;
9068             if (appData.debugMode) 
9069               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9070         }
9071
9072
9073         if (gameInfo.fen != NULL) {
9074           Board initial_position;
9075           startedFromSetupPosition = TRUE;
9076           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9077             Reset(TRUE, TRUE);
9078             DisplayError(_("Bad FEN position in file"), 0);
9079             return FALSE;
9080           }
9081           CopyBoard(boards[0], initial_position);
9082           if (blackPlaysFirst) {
9083             currentMove = forwardMostMove = backwardMostMove = 1;
9084             CopyBoard(boards[1], initial_position);
9085             strcpy(moveList[0], "");
9086             strcpy(parseList[0], "");
9087             timeRemaining[0][1] = whiteTimeRemaining;
9088             timeRemaining[1][1] = blackTimeRemaining;
9089             if (commentList[0] != NULL) {
9090               commentList[1] = commentList[0];
9091               commentList[0] = NULL;
9092             }
9093           } else {
9094             currentMove = forwardMostMove = backwardMostMove = 0;
9095           }
9096           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9097           {   int i;
9098               initialRulePlies = FENrulePlies;
9099               epStatus[forwardMostMove] = FENepStatus;
9100               for( i=0; i< nrCastlingRights; i++ )
9101                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9102           }
9103           yyboardindex = forwardMostMove;
9104           free(gameInfo.fen);
9105           gameInfo.fen = NULL;
9106         }
9107
9108         yyboardindex = forwardMostMove;
9109         cm = (ChessMove) yylex();
9110
9111         /* Handle comments interspersed among the tags */
9112         while (cm == Comment) {
9113             char *p;
9114             if (appData.debugMode) 
9115               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9116             p = yy_text;
9117             if (*p == '{' || *p == '[' || *p == '(') {
9118                 p[strlen(p) - 1] = NULLCHAR;
9119                 p++;
9120             }
9121             while (*p == '\n') p++;
9122             AppendComment(currentMove, p);
9123             yyboardindex = forwardMostMove;
9124             cm = (ChessMove) yylex();
9125         }
9126     }
9127
9128     /* don't rely on existence of Event tag since if game was
9129      * pasted from clipboard the Event tag may not exist
9130      */
9131     if (numPGNTags > 0){
9132         char *tags;
9133         if (gameInfo.variant == VariantNormal) {
9134           gameInfo.variant = StringToVariant(gameInfo.event);
9135         }
9136         if (!matchMode) {
9137           if( appData.autoDisplayTags ) {
9138             tags = PGNTags(&gameInfo);
9139             TagsPopUp(tags, CmailMsg());
9140             free(tags);
9141           }
9142         }
9143     } else {
9144         /* Make something up, but don't display it now */
9145         SetGameInfo();
9146         TagsPopDown();
9147     }
9148
9149     if (cm == PositionDiagram) {
9150         int i, j;
9151         char *p;
9152         Board initial_position;
9153
9154         if (appData.debugMode)
9155           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9156
9157         if (!startedFromSetupPosition) {
9158             p = yy_text;
9159             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9160               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9161                 switch (*p) {
9162                   case '[':
9163                   case '-':
9164                   case ' ':
9165                   case '\t':
9166                   case '\n':
9167                   case '\r':
9168                     break;
9169                   default:
9170                     initial_position[i][j++] = CharToPiece(*p);
9171                     break;
9172                 }
9173             while (*p == ' ' || *p == '\t' ||
9174                    *p == '\n' || *p == '\r') p++;
9175         
9176             if (strncmp(p, "black", strlen("black"))==0)
9177               blackPlaysFirst = TRUE;
9178             else
9179               blackPlaysFirst = FALSE;
9180             startedFromSetupPosition = TRUE;
9181         
9182             CopyBoard(boards[0], initial_position);
9183             if (blackPlaysFirst) {
9184                 currentMove = forwardMostMove = backwardMostMove = 1;
9185                 CopyBoard(boards[1], initial_position);
9186                 strcpy(moveList[0], "");
9187                 strcpy(parseList[0], "");
9188                 timeRemaining[0][1] = whiteTimeRemaining;
9189                 timeRemaining[1][1] = blackTimeRemaining;
9190                 if (commentList[0] != NULL) {
9191                     commentList[1] = commentList[0];
9192                     commentList[0] = NULL;
9193                 }
9194             } else {
9195                 currentMove = forwardMostMove = backwardMostMove = 0;
9196             }
9197         }
9198         yyboardindex = forwardMostMove;
9199         cm = (ChessMove) yylex();
9200     }
9201
9202     if (first.pr == NoProc) {
9203         StartChessProgram(&first);
9204     }
9205     InitChessProgram(&first, FALSE);
9206     SendToProgram("force\n", &first);
9207     if (startedFromSetupPosition) {
9208         SendBoard(&first, forwardMostMove);
9209     if (appData.debugMode) {
9210         fprintf(debugFP, "Load Game\n");
9211     }
9212         DisplayBothClocks();
9213     }      
9214
9215     /* [HGM] server: flag to write setup moves in broadcast file as one */
9216     loadFlag = appData.suppressLoadMoves;
9217
9218     while (cm == Comment) {
9219         char *p;
9220         if (appData.debugMode) 
9221           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9222         p = yy_text;
9223         if (*p == '{' || *p == '[' || *p == '(') {
9224             p[strlen(p) - 1] = NULLCHAR;
9225             p++;
9226         }
9227         while (*p == '\n') p++;
9228         AppendComment(currentMove, p);
9229         yyboardindex = forwardMostMove;
9230         cm = (ChessMove) yylex();
9231     }
9232
9233     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9234         cm == WhiteWins || cm == BlackWins ||
9235         cm == GameIsDrawn || cm == GameUnfinished) {
9236         DisplayMessage("", _("No moves in game"));
9237         if (cmailMsgLoaded) {
9238             if (appData.debugMode)
9239               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9240             ClearHighlights();
9241             flipView = FALSE;
9242         }
9243         DrawPosition(FALSE, boards[currentMove]);
9244         DisplayBothClocks();
9245         gameMode = EditGame;
9246         ModeHighlight();
9247         gameFileFP = NULL;
9248         cmailOldMove = 0;
9249         return TRUE;
9250     }
9251
9252     // [HGM] PV info: routine tests if comment empty
9253     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9254         DisplayComment(currentMove - 1, commentList[currentMove]);
9255     }
9256     if (!matchMode && appData.timeDelay != 0) 
9257       DrawPosition(FALSE, boards[currentMove]);
9258
9259     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9260       programStats.ok_to_send = 1;
9261     }
9262
9263     /* if the first token after the PGN tags is a move
9264      * and not move number 1, retrieve it from the parser 
9265      */
9266     if (cm != MoveNumberOne)
9267         LoadGameOneMove(cm);
9268
9269     /* load the remaining moves from the file */
9270     while (LoadGameOneMove((ChessMove)0)) {
9271       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9272       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9273     }
9274
9275     /* rewind to the start of the game */
9276     currentMove = backwardMostMove;
9277
9278     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9279
9280     if (oldGameMode == AnalyzeFile ||
9281         oldGameMode == AnalyzeMode) {
9282       AnalyzeFileEvent();
9283     }
9284
9285     if (matchMode || appData.timeDelay == 0) {
9286       ToEndEvent();
9287       gameMode = EditGame;
9288       ModeHighlight();
9289     } else if (appData.timeDelay > 0) {
9290       AutoPlayGameLoop();
9291     }
9292
9293     if (appData.debugMode) 
9294         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9295
9296     loadFlag = 0; /* [HGM] true game starts */
9297     return TRUE;
9298 }
9299
9300 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9301 int
9302 ReloadPosition(offset)
9303      int offset;
9304 {
9305     int positionNumber = lastLoadPositionNumber + offset;
9306     if (lastLoadPositionFP == NULL) {
9307         DisplayError(_("No position has been loaded yet"), 0);
9308         return FALSE;
9309     }
9310     if (positionNumber <= 0) {
9311         DisplayError(_("Can't back up any further"), 0);
9312         return FALSE;
9313     }
9314     return LoadPosition(lastLoadPositionFP, positionNumber,
9315                         lastLoadPositionTitle);
9316 }
9317
9318 /* Load the nth position from the given file */
9319 int
9320 LoadPositionFromFile(filename, n, title)
9321      char *filename;
9322      int n;
9323      char *title;
9324 {
9325     FILE *f;
9326     char buf[MSG_SIZ];
9327
9328     if (strcmp(filename, "-") == 0) {
9329         return LoadPosition(stdin, n, "stdin");
9330     } else {
9331         f = fopen(filename, "rb");
9332         if (f == NULL) {
9333             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9334             DisplayError(buf, errno);
9335             return FALSE;
9336         } else {
9337             return LoadPosition(f, n, title);
9338         }
9339     }
9340 }
9341
9342 /* Load the nth position from the given open file, and close it */
9343 int
9344 LoadPosition(f, positionNumber, title)
9345      FILE *f;
9346      int positionNumber;
9347      char *title;
9348 {
9349     char *p, line[MSG_SIZ];
9350     Board initial_position;
9351     int i, j, fenMode, pn;
9352     
9353     if (gameMode == Training )
9354         SetTrainingModeOff();
9355
9356     if (gameMode != BeginningOfGame) {
9357         Reset(FALSE, TRUE);
9358     }
9359     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9360         fclose(lastLoadPositionFP);
9361     }
9362     if (positionNumber == 0) positionNumber = 1;
9363     lastLoadPositionFP = f;
9364     lastLoadPositionNumber = positionNumber;
9365     strcpy(lastLoadPositionTitle, title);
9366     if (first.pr == NoProc) {
9367       StartChessProgram(&first);
9368       InitChessProgram(&first, FALSE);
9369     }    
9370     pn = positionNumber;
9371     if (positionNumber < 0) {
9372         /* Negative position number means to seek to that byte offset */
9373         if (fseek(f, -positionNumber, 0) == -1) {
9374             DisplayError(_("Can't seek on position file"), 0);
9375             return FALSE;
9376         };
9377         pn = 1;
9378     } else {
9379         if (fseek(f, 0, 0) == -1) {
9380             if (f == lastLoadPositionFP ?
9381                 positionNumber == lastLoadPositionNumber + 1 :
9382                 positionNumber == 1) {
9383                 pn = 1;
9384             } else {
9385                 DisplayError(_("Can't seek on position file"), 0);
9386                 return FALSE;
9387             }
9388         }
9389     }
9390     /* See if this file is FEN or old-style xboard */
9391     if (fgets(line, MSG_SIZ, f) == NULL) {
9392         DisplayError(_("Position not found in file"), 0);
9393         return FALSE;
9394     }
9395 #if 0
9396     switch (line[0]) {
9397       case '#':  case 'x':
9398       default:
9399         fenMode = FALSE;
9400         break;
9401       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':
9402       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':
9403       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':
9404       case '7':  case '8':  case '9':
9405       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':
9406       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':
9407       case 'C':  case 'W':             case 'c':  case 'w': 
9408         fenMode = TRUE;
9409         break;
9410     }
9411 #else
9412     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9413     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9414 #endif
9415
9416     if (pn >= 2) {
9417         if (fenMode || line[0] == '#') pn--;
9418         while (pn > 0) {
9419             /* skip positions before number pn */
9420             if (fgets(line, MSG_SIZ, f) == NULL) {
9421                 Reset(TRUE, TRUE);
9422                 DisplayError(_("Position not found in file"), 0);
9423                 return FALSE;
9424             }
9425             if (fenMode || line[0] == '#') pn--;
9426         }
9427     }
9428
9429     if (fenMode) {
9430         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9431             DisplayError(_("Bad FEN position in file"), 0);
9432             return FALSE;
9433         }
9434     } else {
9435         (void) fgets(line, MSG_SIZ, f);
9436         (void) fgets(line, MSG_SIZ, f);
9437     
9438         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9439             (void) fgets(line, MSG_SIZ, f);
9440             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9441                 if (*p == ' ')
9442                   continue;
9443                 initial_position[i][j++] = CharToPiece(*p);
9444             }
9445         }
9446     
9447         blackPlaysFirst = FALSE;
9448         if (!feof(f)) {
9449             (void) fgets(line, MSG_SIZ, f);
9450             if (strncmp(line, "black", strlen("black"))==0)
9451               blackPlaysFirst = TRUE;
9452         }
9453     }
9454     startedFromSetupPosition = TRUE;
9455     
9456     SendToProgram("force\n", &first);
9457     CopyBoard(boards[0], initial_position);
9458     if (blackPlaysFirst) {
9459         currentMove = forwardMostMove = backwardMostMove = 1;
9460         strcpy(moveList[0], "");
9461         strcpy(parseList[0], "");
9462         CopyBoard(boards[1], initial_position);
9463         DisplayMessage("", _("Black to play"));
9464     } else {
9465         currentMove = forwardMostMove = backwardMostMove = 0;
9466         DisplayMessage("", _("White to play"));
9467     }
9468           /* [HGM] copy FEN attributes as well */
9469           {   int i;
9470               initialRulePlies = FENrulePlies;
9471               epStatus[forwardMostMove] = FENepStatus;
9472               for( i=0; i< nrCastlingRights; i++ )
9473                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9474           }
9475     SendBoard(&first, forwardMostMove);
9476     if (appData.debugMode) {
9477 int i, j;
9478   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9479   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9480         fprintf(debugFP, "Load Position\n");
9481     }
9482
9483     if (positionNumber > 1) {
9484         sprintf(line, "%s %d", title, positionNumber);
9485         DisplayTitle(line);
9486     } else {
9487         DisplayTitle(title);
9488     }
9489     gameMode = EditGame;
9490     ModeHighlight();
9491     ResetClocks();
9492     timeRemaining[0][1] = whiteTimeRemaining;
9493     timeRemaining[1][1] = blackTimeRemaining;
9494     DrawPosition(FALSE, boards[currentMove]);
9495    
9496     return TRUE;
9497 }
9498
9499
9500 void
9501 CopyPlayerNameIntoFileName(dest, src)
9502      char **dest, *src;
9503 {
9504     while (*src != NULLCHAR && *src != ',') {
9505         if (*src == ' ') {
9506             *(*dest)++ = '_';
9507             src++;
9508         } else {
9509             *(*dest)++ = *src++;
9510         }
9511     }
9512 }
9513
9514 char *DefaultFileName(ext)
9515      char *ext;
9516 {
9517     static char def[MSG_SIZ];
9518     char *p;
9519
9520     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9521         p = def;
9522         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9523         *p++ = '-';
9524         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9525         *p++ = '.';
9526         strcpy(p, ext);
9527     } else {
9528         def[0] = NULLCHAR;
9529     }
9530     return def;
9531 }
9532
9533 /* Save the current game to the given file */
9534 int
9535 SaveGameToFile(filename, append)
9536      char *filename;
9537      int append;
9538 {
9539     FILE *f;
9540     char buf[MSG_SIZ];
9541
9542     if (strcmp(filename, "-") == 0) {
9543         return SaveGame(stdout, 0, NULL);
9544     } else {
9545         f = fopen(filename, append ? "a" : "w");
9546         if (f == NULL) {
9547             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9548             DisplayError(buf, errno);
9549             return FALSE;
9550         } else {
9551             return SaveGame(f, 0, NULL);
9552         }
9553     }
9554 }
9555
9556 char *
9557 SavePart(str)
9558      char *str;
9559 {
9560     static char buf[MSG_SIZ];
9561     char *p;
9562     
9563     p = strchr(str, ' ');
9564     if (p == NULL) return str;
9565     strncpy(buf, str, p - str);
9566     buf[p - str] = NULLCHAR;
9567     return buf;
9568 }
9569
9570 #define PGN_MAX_LINE 75
9571
9572 #define PGN_SIDE_WHITE  0
9573 #define PGN_SIDE_BLACK  1
9574
9575 /* [AS] */
9576 static int FindFirstMoveOutOfBook( int side )
9577 {
9578     int result = -1;
9579
9580     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9581         int index = backwardMostMove;
9582         int has_book_hit = 0;
9583
9584         if( (index % 2) != side ) {
9585             index++;
9586         }
9587
9588         while( index < forwardMostMove ) {
9589             /* Check to see if engine is in book */
9590             int depth = pvInfoList[index].depth;
9591             int score = pvInfoList[index].score;
9592             int in_book = 0;
9593
9594             if( depth <= 2 ) {
9595                 in_book = 1;
9596             }
9597             else if( score == 0 && depth == 63 ) {
9598                 in_book = 1; /* Zappa */
9599             }
9600             else if( score == 2 && depth == 99 ) {
9601                 in_book = 1; /* Abrok */
9602             }
9603
9604             has_book_hit += in_book;
9605
9606             if( ! in_book ) {
9607                 result = index;
9608
9609                 break;
9610             }
9611
9612             index += 2;
9613         }
9614     }
9615
9616     return result;
9617 }
9618
9619 /* [AS] */
9620 void GetOutOfBookInfo( char * buf )
9621 {
9622     int oob[2];
9623     int i;
9624     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9625
9626     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9627     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9628
9629     *buf = '\0';
9630
9631     if( oob[0] >= 0 || oob[1] >= 0 ) {
9632         for( i=0; i<2; i++ ) {
9633             int idx = oob[i];
9634
9635             if( idx >= 0 ) {
9636                 if( i > 0 && oob[0] >= 0 ) {
9637                     strcat( buf, "   " );
9638                 }
9639
9640                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9641                 sprintf( buf+strlen(buf), "%s%.2f", 
9642                     pvInfoList[idx].score >= 0 ? "+" : "",
9643                     pvInfoList[idx].score / 100.0 );
9644             }
9645         }
9646     }
9647 }
9648
9649 /* Save game in PGN style and close the file */
9650 int
9651 SaveGamePGN(f)
9652      FILE *f;
9653 {
9654     int i, offset, linelen, newblock;
9655     time_t tm;
9656 //    char *movetext;
9657     char numtext[32];
9658     int movelen, numlen, blank;
9659     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9660
9661     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9662     
9663     tm = time((time_t *) NULL);
9664     
9665     PrintPGNTags(f, &gameInfo);
9666     
9667     if (backwardMostMove > 0 || startedFromSetupPosition) {
9668         char *fen = PositionToFEN(backwardMostMove, NULL);
9669         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9670         fprintf(f, "\n{--------------\n");
9671         PrintPosition(f, backwardMostMove);
9672         fprintf(f, "--------------}\n");
9673         free(fen);
9674     }
9675     else {
9676         /* [AS] Out of book annotation */
9677         if( appData.saveOutOfBookInfo ) {
9678             char buf[64];
9679
9680             GetOutOfBookInfo( buf );
9681
9682             if( buf[0] != '\0' ) {
9683                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9684             }
9685         }
9686
9687         fprintf(f, "\n");
9688     }
9689
9690     i = backwardMostMove;
9691     linelen = 0;
9692     newblock = TRUE;
9693
9694     while (i < forwardMostMove) {
9695         /* Print comments preceding this move */
9696         if (commentList[i] != NULL) {
9697             if (linelen > 0) fprintf(f, "\n");
9698             fprintf(f, "{\n%s}\n", commentList[i]);
9699             linelen = 0;
9700             newblock = TRUE;
9701         }
9702
9703         /* Format move number */
9704         if ((i % 2) == 0) {
9705             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9706         } else {
9707             if (newblock) {
9708                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9709             } else {
9710                 numtext[0] = NULLCHAR;
9711             }
9712         }
9713         numlen = strlen(numtext);
9714         newblock = FALSE;
9715
9716         /* Print move number */
9717         blank = linelen > 0 && numlen > 0;
9718         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9719             fprintf(f, "\n");
9720             linelen = 0;
9721             blank = 0;
9722         }
9723         if (blank) {
9724             fprintf(f, " ");
9725             linelen++;
9726         }
9727         fprintf(f, numtext);
9728         linelen += numlen;
9729
9730         /* Get move */
9731         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9732         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9733 #if 0
9734         // SavePart already does this!
9735         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9736                 int p = movelen - 1;
9737                 if(move_buffer[p] == ' ') p--;
9738                 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
9739                     while(p && move_buffer[--p] != '(');
9740                     if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
9741                 }
9742         }
9743 #endif
9744         /* Print move */
9745         blank = linelen > 0 && movelen > 0;
9746         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9747             fprintf(f, "\n");
9748             linelen = 0;
9749             blank = 0;
9750         }
9751         if (blank) {
9752             fprintf(f, " ");
9753             linelen++;
9754         }
9755         fprintf(f, move_buffer);
9756         linelen += movelen;
9757
9758         /* [AS] Add PV info if present */
9759         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9760             /* [HGM] add time */
9761             char buf[MSG_SIZ]; int seconds = 0;
9762
9763 #if 1
9764             if(i >= backwardMostMove) {
9765                 if(WhiteOnMove(i))
9766                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9767                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9768                 else
9769                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9770                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9771             }
9772             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9773 #else
9774             seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
9775 #endif
9776
9777             if( seconds <= 0) buf[0] = 0; else
9778             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9779                 seconds = (seconds + 4)/10; // round to full seconds
9780                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9781                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9782             }
9783
9784             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9785                 pvInfoList[i].score >= 0 ? "+" : "",
9786                 pvInfoList[i].score / 100.0,
9787                 pvInfoList[i].depth,
9788                 buf );
9789
9790             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9791
9792             /* Print score/depth */
9793             blank = linelen > 0 && movelen > 0;
9794             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9795                 fprintf(f, "\n");
9796                 linelen = 0;
9797                 blank = 0;
9798             }
9799             if (blank) {
9800                 fprintf(f, " ");
9801                 linelen++;
9802             }
9803             fprintf(f, move_buffer);
9804             linelen += movelen;
9805         }
9806
9807         i++;
9808     }
9809     
9810     /* Start a new line */
9811     if (linelen > 0) fprintf(f, "\n");
9812
9813     /* Print comments after last move */
9814     if (commentList[i] != NULL) {
9815         fprintf(f, "{\n%s}\n", commentList[i]);
9816     }
9817
9818     /* Print result */
9819     if (gameInfo.resultDetails != NULL &&
9820         gameInfo.resultDetails[0] != NULLCHAR) {
9821         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9822                 PGNResult(gameInfo.result));
9823     } else {
9824         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9825     }
9826
9827     fclose(f);
9828     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9829     return TRUE;
9830 }
9831
9832 /* Save game in old style and close the file */
9833 int
9834 SaveGameOldStyle(f)
9835      FILE *f;
9836 {
9837     int i, offset;
9838     time_t tm;
9839     
9840     tm = time((time_t *) NULL);
9841     
9842     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9843     PrintOpponents(f);
9844     
9845     if (backwardMostMove > 0 || startedFromSetupPosition) {
9846         fprintf(f, "\n[--------------\n");
9847         PrintPosition(f, backwardMostMove);
9848         fprintf(f, "--------------]\n");
9849     } else {
9850         fprintf(f, "\n");
9851     }
9852
9853     i = backwardMostMove;
9854     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9855
9856     while (i < forwardMostMove) {
9857         if (commentList[i] != NULL) {
9858             fprintf(f, "[%s]\n", commentList[i]);
9859         }
9860
9861         if ((i % 2) == 1) {
9862             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
9863             i++;
9864         } else {
9865             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
9866             i++;
9867             if (commentList[i] != NULL) {
9868                 fprintf(f, "\n");
9869                 continue;
9870             }
9871             if (i >= forwardMostMove) {
9872                 fprintf(f, "\n");
9873                 break;
9874             }
9875             fprintf(f, "%s\n", parseList[i]);
9876             i++;
9877         }
9878     }
9879     
9880     if (commentList[i] != NULL) {
9881         fprintf(f, "[%s]\n", commentList[i]);
9882     }
9883
9884     /* This isn't really the old style, but it's close enough */
9885     if (gameInfo.resultDetails != NULL &&
9886         gameInfo.resultDetails[0] != NULLCHAR) {
9887         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9888                 gameInfo.resultDetails);
9889     } else {
9890         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9891     }
9892
9893     fclose(f);
9894     return TRUE;
9895 }
9896
9897 /* Save the current game to open file f and close the file */
9898 int
9899 SaveGame(f, dummy, dummy2)
9900      FILE *f;
9901      int dummy;
9902      char *dummy2;
9903 {
9904     if (gameMode == EditPosition) EditPositionDone();
9905     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9906     if (appData.oldSaveStyle)
9907       return SaveGameOldStyle(f);
9908     else
9909       return SaveGamePGN(f);
9910 }
9911
9912 /* Save the current position to the given file */
9913 int
9914 SavePositionToFile(filename)
9915      char *filename;
9916 {
9917     FILE *f;
9918     char buf[MSG_SIZ];
9919
9920     if (strcmp(filename, "-") == 0) {
9921         return SavePosition(stdout, 0, NULL);
9922     } else {
9923         f = fopen(filename, "a");
9924         if (f == NULL) {
9925             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9926             DisplayError(buf, errno);
9927             return FALSE;
9928         } else {
9929             SavePosition(f, 0, NULL);
9930             return TRUE;
9931         }
9932     }
9933 }
9934
9935 /* Save the current position to the given open file and close the file */
9936 int
9937 SavePosition(f, dummy, dummy2)
9938      FILE *f;
9939      int dummy;
9940      char *dummy2;
9941 {
9942     time_t tm;
9943     char *fen;
9944     
9945     if (appData.oldSaveStyle) {
9946         tm = time((time_t *) NULL);
9947     
9948         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9949         PrintOpponents(f);
9950         fprintf(f, "[--------------\n");
9951         PrintPosition(f, currentMove);
9952         fprintf(f, "--------------]\n");
9953     } else {
9954         fen = PositionToFEN(currentMove, NULL);
9955         fprintf(f, "%s\n", fen);
9956         free(fen);
9957     }
9958     fclose(f);
9959     return TRUE;
9960 }
9961
9962 void
9963 ReloadCmailMsgEvent(unregister)
9964      int unregister;
9965 {
9966 #if !WIN32
9967     static char *inFilename = NULL;
9968     static char *outFilename;
9969     int i;
9970     struct stat inbuf, outbuf;
9971     int status;
9972     
9973     /* Any registered moves are unregistered if unregister is set, */
9974     /* i.e. invoked by the signal handler */
9975     if (unregister) {
9976         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9977             cmailMoveRegistered[i] = FALSE;
9978             if (cmailCommentList[i] != NULL) {
9979                 free(cmailCommentList[i]);
9980                 cmailCommentList[i] = NULL;
9981             }
9982         }
9983         nCmailMovesRegistered = 0;
9984     }
9985
9986     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9987         cmailResult[i] = CMAIL_NOT_RESULT;
9988     }
9989     nCmailResults = 0;
9990
9991     if (inFilename == NULL) {
9992         /* Because the filenames are static they only get malloced once  */
9993         /* and they never get freed                                      */
9994         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9995         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9996
9997         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9998         sprintf(outFilename, "%s.out", appData.cmailGameName);
9999     }
10000     
10001     status = stat(outFilename, &outbuf);
10002     if (status < 0) {
10003         cmailMailedMove = FALSE;
10004     } else {
10005         status = stat(inFilename, &inbuf);
10006         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10007     }
10008     
10009     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10010        counts the games, notes how each one terminated, etc.
10011        
10012        It would be nice to remove this kludge and instead gather all
10013        the information while building the game list.  (And to keep it
10014        in the game list nodes instead of having a bunch of fixed-size
10015        parallel arrays.)  Note this will require getting each game's
10016        termination from the PGN tags, as the game list builder does
10017        not process the game moves.  --mann
10018        */
10019     cmailMsgLoaded = TRUE;
10020     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10021     
10022     /* Load first game in the file or popup game menu */
10023     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10024
10025 #endif /* !WIN32 */
10026     return;
10027 }
10028
10029 int
10030 RegisterMove()
10031 {
10032     FILE *f;
10033     char string[MSG_SIZ];
10034
10035     if (   cmailMailedMove
10036         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10037         return TRUE;            /* Allow free viewing  */
10038     }
10039
10040     /* Unregister move to ensure that we don't leave RegisterMove        */
10041     /* with the move registered when the conditions for registering no   */
10042     /* longer hold                                                       */
10043     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10044         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10045         nCmailMovesRegistered --;
10046
10047         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10048           {
10049               free(cmailCommentList[lastLoadGameNumber - 1]);
10050               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10051           }
10052     }
10053
10054     if (cmailOldMove == -1) {
10055         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10056         return FALSE;
10057     }
10058
10059     if (currentMove > cmailOldMove + 1) {
10060         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10061         return FALSE;
10062     }
10063
10064     if (currentMove < cmailOldMove) {
10065         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10066         return FALSE;
10067     }
10068
10069     if (forwardMostMove > currentMove) {
10070         /* Silently truncate extra moves */
10071         TruncateGame();
10072     }
10073
10074     if (   (currentMove == cmailOldMove + 1)
10075         || (   (currentMove == cmailOldMove)
10076             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10077                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10078         if (gameInfo.result != GameUnfinished) {
10079             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10080         }
10081
10082         if (commentList[currentMove] != NULL) {
10083             cmailCommentList[lastLoadGameNumber - 1]
10084               = StrSave(commentList[currentMove]);
10085         }
10086         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10087
10088         if (appData.debugMode)
10089           fprintf(debugFP, "Saving %s for game %d\n",
10090                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10091
10092         sprintf(string,
10093                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10094         
10095         f = fopen(string, "w");
10096         if (appData.oldSaveStyle) {
10097             SaveGameOldStyle(f); /* also closes the file */
10098             
10099             sprintf(string, "%s.pos.out", appData.cmailGameName);
10100             f = fopen(string, "w");
10101             SavePosition(f, 0, NULL); /* also closes the file */
10102         } else {
10103             fprintf(f, "{--------------\n");
10104             PrintPosition(f, currentMove);
10105             fprintf(f, "--------------}\n\n");
10106             
10107             SaveGame(f, 0, NULL); /* also closes the file*/
10108         }
10109         
10110         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10111         nCmailMovesRegistered ++;
10112     } else if (nCmailGames == 1) {
10113         DisplayError(_("You have not made a move yet"), 0);
10114         return FALSE;
10115     }
10116
10117     return TRUE;
10118 }
10119
10120 void
10121 MailMoveEvent()
10122 {
10123 #if !WIN32
10124     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10125     FILE *commandOutput;
10126     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10127     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10128     int nBuffers;
10129     int i;
10130     int archived;
10131     char *arcDir;
10132
10133     if (! cmailMsgLoaded) {
10134         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10135         return;
10136     }
10137
10138     if (nCmailGames == nCmailResults) {
10139         DisplayError(_("No unfinished games"), 0);
10140         return;
10141     }
10142
10143 #if CMAIL_PROHIBIT_REMAIL
10144     if (cmailMailedMove) {
10145         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);
10146         DisplayError(msg, 0);
10147         return;
10148     }
10149 #endif
10150
10151     if (! (cmailMailedMove || RegisterMove())) return;
10152     
10153     if (   cmailMailedMove
10154         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10155         sprintf(string, partCommandString,
10156                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10157         commandOutput = popen(string, "r");
10158
10159         if (commandOutput == NULL) {
10160             DisplayError(_("Failed to invoke cmail"), 0);
10161         } else {
10162             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10163                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10164             }
10165             if (nBuffers > 1) {
10166                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10167                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10168                 nBytes = MSG_SIZ - 1;
10169             } else {
10170                 (void) memcpy(msg, buffer, nBytes);
10171             }
10172             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10173
10174             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10175                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10176
10177                 archived = TRUE;
10178                 for (i = 0; i < nCmailGames; i ++) {
10179                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10180                         archived = FALSE;
10181                     }
10182                 }
10183                 if (   archived
10184                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10185                         != NULL)) {
10186                     sprintf(buffer, "%s/%s.%s.archive",
10187                             arcDir,
10188                             appData.cmailGameName,
10189                             gameInfo.date);
10190                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10191                     cmailMsgLoaded = FALSE;
10192                 }
10193             }
10194
10195             DisplayInformation(msg);
10196             pclose(commandOutput);
10197         }
10198     } else {
10199         if ((*cmailMsg) != '\0') {
10200             DisplayInformation(cmailMsg);
10201         }
10202     }
10203
10204     return;
10205 #endif /* !WIN32 */
10206 }
10207
10208 char *
10209 CmailMsg()
10210 {
10211 #if WIN32
10212     return NULL;
10213 #else
10214     int  prependComma = 0;
10215     char number[5];
10216     char string[MSG_SIZ];       /* Space for game-list */
10217     int  i;
10218     
10219     if (!cmailMsgLoaded) return "";
10220
10221     if (cmailMailedMove) {
10222         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10223     } else {
10224         /* Create a list of games left */
10225         sprintf(string, "[");
10226         for (i = 0; i < nCmailGames; i ++) {
10227             if (! (   cmailMoveRegistered[i]
10228                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10229                 if (prependComma) {
10230                     sprintf(number, ",%d", i + 1);
10231                 } else {
10232                     sprintf(number, "%d", i + 1);
10233                     prependComma = 1;
10234                 }
10235                 
10236                 strcat(string, number);
10237             }
10238         }
10239         strcat(string, "]");
10240
10241         if (nCmailMovesRegistered + nCmailResults == 0) {
10242             switch (nCmailGames) {
10243               case 1:
10244                 sprintf(cmailMsg,
10245                         _("Still need to make move for game\n"));
10246                 break;
10247                 
10248               case 2:
10249                 sprintf(cmailMsg,
10250                         _("Still need to make moves for both games\n"));
10251                 break;
10252                 
10253               default:
10254                 sprintf(cmailMsg,
10255                         _("Still need to make moves for all %d games\n"),
10256                         nCmailGames);
10257                 break;
10258             }
10259         } else {
10260             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10261               case 1:
10262                 sprintf(cmailMsg,
10263                         _("Still need to make a move for game %s\n"),
10264                         string);
10265                 break;
10266                 
10267               case 0:
10268                 if (nCmailResults == nCmailGames) {
10269                     sprintf(cmailMsg, _("No unfinished games\n"));
10270                 } else {
10271                     sprintf(cmailMsg, _("Ready to send mail\n"));
10272                 }
10273                 break;
10274                 
10275               default:
10276                 sprintf(cmailMsg,
10277                         _("Still need to make moves for games %s\n"),
10278                         string);
10279             }
10280         }
10281     }
10282     return cmailMsg;
10283 #endif /* WIN32 */
10284 }
10285
10286 void
10287 ResetGameEvent()
10288 {
10289     if (gameMode == Training)
10290       SetTrainingModeOff();
10291
10292     Reset(TRUE, TRUE);
10293     cmailMsgLoaded = FALSE;
10294     if (appData.icsActive) {
10295       SendToICS(ics_prefix);
10296       SendToICS("refresh\n");
10297     }
10298 }
10299
10300 void
10301 ExitEvent(status)
10302      int status;
10303 {
10304     exiting++;
10305     if (exiting > 2) {
10306       /* Give up on clean exit */
10307       exit(status);
10308     }
10309     if (exiting > 1) {
10310       /* Keep trying for clean exit */
10311       return;
10312     }
10313
10314     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10315
10316     if (telnetISR != NULL) {
10317       RemoveInputSource(telnetISR);
10318     }
10319     if (icsPR != NoProc) {
10320       DestroyChildProcess(icsPR, TRUE);
10321     }
10322 #if 0
10323     /* Save game if resource set and not already saved by GameEnds() */
10324     if ((gameInfo.resultDetails == NULL || errorExitFlag )
10325                              && forwardMostMove > 0) {
10326       if (*appData.saveGameFile != NULLCHAR) {
10327         SaveGameToFile(appData.saveGameFile, TRUE);
10328       } else if (appData.autoSaveGames) {
10329         AutoSaveGame();
10330       }
10331       if (*appData.savePositionFile != NULLCHAR) {
10332         SavePositionToFile(appData.savePositionFile);
10333       }
10334     }
10335     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10336 #else
10337     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10338     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10339 #endif
10340     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10341     /* make sure this other one finishes before killing it!                  */
10342     if(endingGame) { int count = 0;
10343         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10344         while(endingGame && count++ < 10) DoSleep(1);
10345         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10346     }
10347
10348     /* Kill off chess programs */
10349     if (first.pr != NoProc) {
10350         ExitAnalyzeMode();
10351         
10352         DoSleep( appData.delayBeforeQuit );
10353         SendToProgram("quit\n", &first);
10354         DoSleep( appData.delayAfterQuit );
10355         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10356     }
10357     if (second.pr != NoProc) {
10358         DoSleep( appData.delayBeforeQuit );
10359         SendToProgram("quit\n", &second);
10360         DoSleep( appData.delayAfterQuit );
10361         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10362     }
10363     if (first.isr != NULL) {
10364         RemoveInputSource(first.isr);
10365     }
10366     if (second.isr != NULL) {
10367         RemoveInputSource(second.isr);
10368     }
10369
10370     ShutDownFrontEnd();
10371     exit(status);
10372 }
10373
10374 void
10375 PauseEvent()
10376 {
10377     if (appData.debugMode)
10378         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10379     if (pausing) {
10380         pausing = FALSE;
10381         ModeHighlight();
10382         if (gameMode == MachinePlaysWhite ||
10383             gameMode == MachinePlaysBlack) {
10384             StartClocks();
10385         } else {
10386             DisplayBothClocks();
10387         }
10388         if (gameMode == PlayFromGameFile) {
10389             if (appData.timeDelay >= 0) 
10390                 AutoPlayGameLoop();
10391         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10392             Reset(FALSE, TRUE);
10393             SendToICS(ics_prefix);
10394             SendToICS("refresh\n");
10395         } else if (currentMove < forwardMostMove) {
10396             ForwardInner(forwardMostMove);
10397         }
10398         pauseExamInvalid = FALSE;
10399     } else {
10400         switch (gameMode) {
10401           default:
10402             return;
10403           case IcsExamining:
10404             pauseExamForwardMostMove = forwardMostMove;
10405             pauseExamInvalid = FALSE;
10406             /* fall through */
10407           case IcsObserving:
10408           case IcsPlayingWhite:
10409           case IcsPlayingBlack:
10410             pausing = TRUE;
10411             ModeHighlight();
10412             return;
10413           case PlayFromGameFile:
10414             (void) StopLoadGameTimer();
10415             pausing = TRUE;
10416             ModeHighlight();
10417             break;
10418           case BeginningOfGame:
10419             if (appData.icsActive) return;
10420             /* else fall through */
10421           case MachinePlaysWhite:
10422           case MachinePlaysBlack:
10423           case TwoMachinesPlay:
10424             if (forwardMostMove == 0)
10425               return;           /* don't pause if no one has moved */
10426             if ((gameMode == MachinePlaysWhite &&
10427                  !WhiteOnMove(forwardMostMove)) ||
10428                 (gameMode == MachinePlaysBlack &&
10429                  WhiteOnMove(forwardMostMove))) {
10430                 StopClocks();
10431             }
10432             pausing = TRUE;
10433             ModeHighlight();
10434             break;
10435         }
10436     }
10437 }
10438
10439 void
10440 EditCommentEvent()
10441 {
10442     char title[MSG_SIZ];
10443
10444     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10445         strcpy(title, _("Edit comment"));
10446     } else {
10447         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10448                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10449                 parseList[currentMove - 1]);
10450     }
10451
10452     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10453 }
10454
10455
10456 void
10457 EditTagsEvent()
10458 {
10459     char *tags = PGNTags(&gameInfo);
10460     EditTagsPopUp(tags);
10461     free(tags);
10462 }
10463
10464 void
10465 AnalyzeModeEvent()
10466 {
10467     if (appData.noChessProgram || gameMode == AnalyzeMode)
10468       return;
10469
10470     if (gameMode != AnalyzeFile) {
10471         if (!appData.icsEngineAnalyze) {
10472                EditGameEvent();
10473                if (gameMode != EditGame) return;
10474         }
10475         ResurrectChessProgram();
10476         SendToProgram("analyze\n", &first);
10477         first.analyzing = TRUE;
10478         /*first.maybeThinking = TRUE;*/
10479         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10480         AnalysisPopUp(_("Analysis"),
10481                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10482     }
10483     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10484     pausing = FALSE;
10485     ModeHighlight();
10486     SetGameInfo();
10487
10488     StartAnalysisClock();
10489     GetTimeMark(&lastNodeCountTime);
10490     lastNodeCount = 0;
10491 }
10492
10493 void
10494 AnalyzeFileEvent()
10495 {
10496     if (appData.noChessProgram || gameMode == AnalyzeFile)
10497       return;
10498
10499     if (gameMode != AnalyzeMode) {
10500         EditGameEvent();
10501         if (gameMode != EditGame) return;
10502         ResurrectChessProgram();
10503         SendToProgram("analyze\n", &first);
10504         first.analyzing = TRUE;
10505         /*first.maybeThinking = TRUE;*/
10506         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10507         AnalysisPopUp(_("Analysis"),
10508                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10509     }
10510     gameMode = AnalyzeFile;
10511     pausing = FALSE;
10512     ModeHighlight();
10513     SetGameInfo();
10514
10515     StartAnalysisClock();
10516     GetTimeMark(&lastNodeCountTime);
10517     lastNodeCount = 0;
10518 }
10519
10520 void
10521 MachineWhiteEvent()
10522 {
10523     char buf[MSG_SIZ];
10524     char *bookHit = NULL;
10525
10526     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10527       return;
10528
10529
10530     if (gameMode == PlayFromGameFile || 
10531         gameMode == TwoMachinesPlay  || 
10532         gameMode == Training         || 
10533         gameMode == AnalyzeMode      || 
10534         gameMode == EndOfGame)
10535         EditGameEvent();
10536
10537     if (gameMode == EditPosition) 
10538         EditPositionDone();
10539
10540     if (!WhiteOnMove(currentMove)) {
10541         DisplayError(_("It is not White's turn"), 0);
10542         return;
10543     }
10544   
10545     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10546       ExitAnalyzeMode();
10547
10548     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10549         gameMode == AnalyzeFile)
10550         TruncateGame();
10551
10552     ResurrectChessProgram();    /* in case it isn't running */
10553     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10554         gameMode = MachinePlaysWhite;
10555         ResetClocks();
10556     } else
10557     gameMode = MachinePlaysWhite;
10558     pausing = FALSE;
10559     ModeHighlight();
10560     SetGameInfo();
10561     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10562     DisplayTitle(buf);
10563     if (first.sendName) {
10564       sprintf(buf, "name %s\n", gameInfo.black);
10565       SendToProgram(buf, &first);
10566     }
10567     if (first.sendTime) {
10568       if (first.useColors) {
10569         SendToProgram("black\n", &first); /*gnu kludge*/
10570       }
10571       SendTimeRemaining(&first, TRUE);
10572     }
10573     if (first.useColors) {
10574       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10575     }
10576     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10577     SetMachineThinkingEnables();
10578     first.maybeThinking = TRUE;
10579     StartClocks();
10580     firstMove = FALSE;
10581
10582     if (appData.autoFlipView && !flipView) {
10583       flipView = !flipView;
10584       DrawPosition(FALSE, NULL);
10585       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10586     }
10587
10588     if(bookHit) { // [HGM] book: simulate book reply
10589         static char bookMove[MSG_SIZ]; // a bit generous?
10590
10591         programStats.nodes = programStats.depth = programStats.time = 
10592         programStats.score = programStats.got_only_move = 0;
10593         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10594
10595         strcpy(bookMove, "move ");
10596         strcat(bookMove, bookHit);
10597         HandleMachineMove(bookMove, &first);
10598     }
10599 }
10600
10601 void
10602 MachineBlackEvent()
10603 {
10604     char buf[MSG_SIZ];
10605    char *bookHit = NULL;
10606
10607     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10608         return;
10609
10610
10611     if (gameMode == PlayFromGameFile || 
10612         gameMode == TwoMachinesPlay  || 
10613         gameMode == Training         || 
10614         gameMode == AnalyzeMode      || 
10615         gameMode == EndOfGame)
10616         EditGameEvent();
10617
10618     if (gameMode == EditPosition) 
10619         EditPositionDone();
10620
10621     if (WhiteOnMove(currentMove)) {
10622         DisplayError(_("It is not Black's turn"), 0);
10623         return;
10624     }
10625     
10626     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10627       ExitAnalyzeMode();
10628
10629     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10630         gameMode == AnalyzeFile)
10631         TruncateGame();
10632
10633     ResurrectChessProgram();    /* in case it isn't running */
10634     gameMode = MachinePlaysBlack;
10635     pausing = FALSE;
10636     ModeHighlight();
10637     SetGameInfo();
10638     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10639     DisplayTitle(buf);
10640     if (first.sendName) {
10641       sprintf(buf, "name %s\n", gameInfo.white);
10642       SendToProgram(buf, &first);
10643     }
10644     if (first.sendTime) {
10645       if (first.useColors) {
10646         SendToProgram("white\n", &first); /*gnu kludge*/
10647       }
10648       SendTimeRemaining(&first, FALSE);
10649     }
10650     if (first.useColors) {
10651       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10652     }
10653     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10654     SetMachineThinkingEnables();
10655     first.maybeThinking = TRUE;
10656     StartClocks();
10657
10658     if (appData.autoFlipView && flipView) {
10659       flipView = !flipView;
10660       DrawPosition(FALSE, NULL);
10661       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10662     }
10663     if(bookHit) { // [HGM] book: simulate book reply
10664         static char bookMove[MSG_SIZ]; // a bit generous?
10665
10666         programStats.nodes = programStats.depth = programStats.time = 
10667         programStats.score = programStats.got_only_move = 0;
10668         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10669
10670         strcpy(bookMove, "move ");
10671         strcat(bookMove, bookHit);
10672         HandleMachineMove(bookMove, &first);
10673     }
10674 }
10675
10676
10677 void
10678 DisplayTwoMachinesTitle()
10679 {
10680     char buf[MSG_SIZ];
10681     if (appData.matchGames > 0) {
10682         if (first.twoMachinesColor[0] == 'w') {
10683             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10684                     gameInfo.white, gameInfo.black,
10685                     first.matchWins, second.matchWins,
10686                     matchGame - 1 - (first.matchWins + second.matchWins));
10687         } else {
10688             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10689                     gameInfo.white, gameInfo.black,
10690                     second.matchWins, first.matchWins,
10691                     matchGame - 1 - (first.matchWins + second.matchWins));
10692         }
10693     } else {
10694         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10695     }
10696     DisplayTitle(buf);
10697 }
10698
10699 void
10700 TwoMachinesEvent P((void))
10701 {
10702     int i;
10703     char buf[MSG_SIZ];
10704     ChessProgramState *onmove;
10705     char *bookHit = NULL;
10706     
10707     if (appData.noChessProgram) return;
10708
10709     switch (gameMode) {
10710       case TwoMachinesPlay:
10711         return;
10712       case MachinePlaysWhite:
10713       case MachinePlaysBlack:
10714         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10715             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10716             return;
10717         }
10718         /* fall through */
10719       case BeginningOfGame:
10720       case PlayFromGameFile:
10721       case EndOfGame:
10722         EditGameEvent();
10723         if (gameMode != EditGame) return;
10724         break;
10725       case EditPosition:
10726         EditPositionDone();
10727         break;
10728       case AnalyzeMode:
10729       case AnalyzeFile:
10730         ExitAnalyzeMode();
10731         break;
10732       case EditGame:
10733       default:
10734         break;
10735     }
10736
10737     forwardMostMove = currentMove;
10738     ResurrectChessProgram();    /* in case first program isn't running */
10739
10740     if (second.pr == NULL) {
10741         StartChessProgram(&second);
10742         if (second.protocolVersion == 1) {
10743           TwoMachinesEventIfReady();
10744         } else {
10745           /* kludge: allow timeout for initial "feature" command */
10746           FreezeUI();
10747           DisplayMessage("", _("Starting second chess program"));
10748           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10749         }
10750         return;
10751     }
10752     DisplayMessage("", "");
10753     InitChessProgram(&second, FALSE);
10754     SendToProgram("force\n", &second);
10755     if (startedFromSetupPosition) {
10756         SendBoard(&second, backwardMostMove);
10757     if (appData.debugMode) {
10758         fprintf(debugFP, "Two Machines\n");
10759     }
10760     }
10761     for (i = backwardMostMove; i < forwardMostMove; i++) {
10762         SendMoveToProgram(i, &second);
10763     }
10764
10765     gameMode = TwoMachinesPlay;
10766     pausing = FALSE;
10767     ModeHighlight();
10768     SetGameInfo();
10769     DisplayTwoMachinesTitle();
10770     firstMove = TRUE;
10771     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10772         onmove = &first;
10773     } else {
10774         onmove = &second;
10775     }
10776
10777     SendToProgram(first.computerString, &first);
10778     if (first.sendName) {
10779       sprintf(buf, "name %s\n", second.tidy);
10780       SendToProgram(buf, &first);
10781     }
10782     SendToProgram(second.computerString, &second);
10783     if (second.sendName) {
10784       sprintf(buf, "name %s\n", first.tidy);
10785       SendToProgram(buf, &second);
10786     }
10787
10788     ResetClocks();
10789     if (!first.sendTime || !second.sendTime) {
10790         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10791         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10792     }
10793     if (onmove->sendTime) {
10794       if (onmove->useColors) {
10795         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10796       }
10797       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10798     }
10799     if (onmove->useColors) {
10800       SendToProgram(onmove->twoMachinesColor, onmove);
10801     }
10802     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10803 //    SendToProgram("go\n", onmove);
10804     onmove->maybeThinking = TRUE;
10805     SetMachineThinkingEnables();
10806
10807     StartClocks();
10808
10809     if(bookHit) { // [HGM] book: simulate book reply
10810         static char bookMove[MSG_SIZ]; // a bit generous?
10811
10812         programStats.nodes = programStats.depth = programStats.time = 
10813         programStats.score = programStats.got_only_move = 0;
10814         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10815
10816         strcpy(bookMove, "move ");
10817         strcat(bookMove, bookHit);
10818         HandleMachineMove(bookMove, &first);
10819     }
10820 }
10821
10822 void
10823 TrainingEvent()
10824 {
10825     if (gameMode == Training) {
10826       SetTrainingModeOff();
10827       gameMode = PlayFromGameFile;
10828       DisplayMessage("", _("Training mode off"));
10829     } else {
10830       gameMode = Training;
10831       animateTraining = appData.animate;
10832
10833       /* make sure we are not already at the end of the game */
10834       if (currentMove < forwardMostMove) {
10835         SetTrainingModeOn();
10836         DisplayMessage("", _("Training mode on"));
10837       } else {
10838         gameMode = PlayFromGameFile;
10839         DisplayError(_("Already at end of game"), 0);
10840       }
10841     }
10842     ModeHighlight();
10843 }
10844
10845 void
10846 IcsClientEvent()
10847 {
10848     if (!appData.icsActive) return;
10849     switch (gameMode) {
10850       case IcsPlayingWhite:
10851       case IcsPlayingBlack:
10852       case IcsObserving:
10853       case IcsIdle:
10854       case BeginningOfGame:
10855       case IcsExamining:
10856         return;
10857
10858       case EditGame:
10859         break;
10860
10861       case EditPosition:
10862         EditPositionDone();
10863         break;
10864
10865       case AnalyzeMode:
10866       case AnalyzeFile:
10867         ExitAnalyzeMode();
10868         break;
10869         
10870       default:
10871         EditGameEvent();
10872         break;
10873     }
10874
10875     gameMode = IcsIdle;
10876     ModeHighlight();
10877     return;
10878 }
10879
10880
10881 void
10882 EditGameEvent()
10883 {
10884     int i;
10885
10886     switch (gameMode) {
10887       case Training:
10888         SetTrainingModeOff();
10889         break;
10890       case MachinePlaysWhite:
10891       case MachinePlaysBlack:
10892       case BeginningOfGame:
10893         SendToProgram("force\n", &first);
10894         SetUserThinkingEnables();
10895         break;
10896       case PlayFromGameFile:
10897         (void) StopLoadGameTimer();
10898         if (gameFileFP != NULL) {
10899             gameFileFP = NULL;
10900         }
10901         break;
10902       case EditPosition:
10903         EditPositionDone();
10904         break;
10905       case AnalyzeMode:
10906       case AnalyzeFile:
10907         ExitAnalyzeMode();
10908         SendToProgram("force\n", &first);
10909         break;
10910       case TwoMachinesPlay:
10911         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10912         ResurrectChessProgram();
10913         SetUserThinkingEnables();
10914         break;
10915       case EndOfGame:
10916         ResurrectChessProgram();
10917         break;
10918       case IcsPlayingBlack:
10919       case IcsPlayingWhite:
10920         DisplayError(_("Warning: You are still playing a game"), 0);
10921         break;
10922       case IcsObserving:
10923         DisplayError(_("Warning: You are still observing a game"), 0);
10924         break;
10925       case IcsExamining:
10926         DisplayError(_("Warning: You are still examining a game"), 0);
10927         break;
10928       case IcsIdle:
10929         break;
10930       case EditGame:
10931       default:
10932         return;
10933     }
10934     
10935     pausing = FALSE;
10936     StopClocks();
10937     first.offeredDraw = second.offeredDraw = 0;
10938
10939     if (gameMode == PlayFromGameFile) {
10940         whiteTimeRemaining = timeRemaining[0][currentMove];
10941         blackTimeRemaining = timeRemaining[1][currentMove];
10942         DisplayTitle("");
10943     }
10944
10945     if (gameMode == MachinePlaysWhite ||
10946         gameMode == MachinePlaysBlack ||
10947         gameMode == TwoMachinesPlay ||
10948         gameMode == EndOfGame) {
10949         i = forwardMostMove;
10950         while (i > currentMove) {
10951             SendToProgram("undo\n", &first);
10952             i--;
10953         }
10954         whiteTimeRemaining = timeRemaining[0][currentMove];
10955         blackTimeRemaining = timeRemaining[1][currentMove];
10956         DisplayBothClocks();
10957         if (whiteFlag || blackFlag) {
10958             whiteFlag = blackFlag = 0;
10959         }
10960         DisplayTitle("");
10961     }           
10962     
10963     gameMode = EditGame;
10964     ModeHighlight();
10965     SetGameInfo();
10966 }
10967
10968
10969 void
10970 EditPositionEvent()
10971 {
10972     if (gameMode == EditPosition) {
10973         EditGameEvent();
10974         return;
10975     }
10976     
10977     EditGameEvent();
10978     if (gameMode != EditGame) return;
10979     
10980     gameMode = EditPosition;
10981     ModeHighlight();
10982     SetGameInfo();
10983     if (currentMove > 0)
10984       CopyBoard(boards[0], boards[currentMove]);
10985     
10986     blackPlaysFirst = !WhiteOnMove(currentMove);
10987     ResetClocks();
10988     currentMove = forwardMostMove = backwardMostMove = 0;
10989     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10990     DisplayMove(-1);
10991 }
10992
10993 void
10994 ExitAnalyzeMode()
10995 {
10996     /* [DM] icsEngineAnalyze - possible call from other functions */
10997     if (appData.icsEngineAnalyze) {
10998         appData.icsEngineAnalyze = FALSE;
10999
11000         DisplayMessage("",_("Close ICS engine analyze..."));
11001     }
11002     if (first.analysisSupport && first.analyzing) {
11003       SendToProgram("exit\n", &first);
11004       first.analyzing = FALSE;
11005     }
11006     AnalysisPopDown();
11007     thinkOutput[0] = NULLCHAR;
11008 }
11009
11010 void
11011 EditPositionDone()
11012 {
11013     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11014
11015     startedFromSetupPosition = TRUE;
11016     InitChessProgram(&first, FALSE);
11017     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11018     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11019         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11020         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11021     } else castlingRights[0][2] = -1;
11022     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11023         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11024         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11025     } else castlingRights[0][5] = -1;
11026     SendToProgram("force\n", &first);
11027     if (blackPlaysFirst) {
11028         strcpy(moveList[0], "");
11029         strcpy(parseList[0], "");
11030         currentMove = forwardMostMove = backwardMostMove = 1;
11031         CopyBoard(boards[1], boards[0]);
11032         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11033         { int i;
11034           epStatus[1] = epStatus[0];
11035           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11036         }
11037     } else {
11038         currentMove = forwardMostMove = backwardMostMove = 0;
11039     }
11040     SendBoard(&first, forwardMostMove);
11041     if (appData.debugMode) {
11042         fprintf(debugFP, "EditPosDone\n");
11043     }
11044     DisplayTitle("");
11045     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11046     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11047     gameMode = EditGame;
11048     ModeHighlight();
11049     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11050     ClearHighlights(); /* [AS] */
11051 }
11052
11053 /* Pause for `ms' milliseconds */
11054 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11055 void
11056 TimeDelay(ms)
11057      long ms;
11058 {
11059     TimeMark m1, m2;
11060
11061     GetTimeMark(&m1);
11062     do {
11063         GetTimeMark(&m2);
11064     } while (SubtractTimeMarks(&m2, &m1) < ms);
11065 }
11066
11067 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11068 void
11069 SendMultiLineToICS(buf)
11070      char *buf;
11071 {
11072     char temp[MSG_SIZ+1], *p;
11073     int len;
11074
11075     len = strlen(buf);
11076     if (len > MSG_SIZ)
11077       len = MSG_SIZ;
11078   
11079     strncpy(temp, buf, len);
11080     temp[len] = 0;
11081
11082     p = temp;
11083     while (*p) {
11084         if (*p == '\n' || *p == '\r')
11085           *p = ' ';
11086         ++p;
11087     }
11088
11089     strcat(temp, "\n");
11090     SendToICS(temp);
11091     SendToPlayer(temp, strlen(temp));
11092 }
11093
11094 void
11095 SetWhiteToPlayEvent()
11096 {
11097     if (gameMode == EditPosition) {
11098         blackPlaysFirst = FALSE;
11099         DisplayBothClocks();    /* works because currentMove is 0 */
11100     } else if (gameMode == IcsExamining) {
11101         SendToICS(ics_prefix);
11102         SendToICS("tomove white\n");
11103     }
11104 }
11105
11106 void
11107 SetBlackToPlayEvent()
11108 {
11109     if (gameMode == EditPosition) {
11110         blackPlaysFirst = TRUE;
11111         currentMove = 1;        /* kludge */
11112         DisplayBothClocks();
11113         currentMove = 0;
11114     } else if (gameMode == IcsExamining) {
11115         SendToICS(ics_prefix);
11116         SendToICS("tomove black\n");
11117     }
11118 }
11119
11120 void
11121 EditPositionMenuEvent(selection, x, y)
11122      ChessSquare selection;
11123      int x, y;
11124 {
11125     char buf[MSG_SIZ];
11126     ChessSquare piece = boards[0][y][x];
11127
11128     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11129
11130     switch (selection) {
11131       case ClearBoard:
11132         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11133             SendToICS(ics_prefix);
11134             SendToICS("bsetup clear\n");
11135         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11136             SendToICS(ics_prefix);
11137             SendToICS("clearboard\n");
11138         } else {
11139             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11140                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11141                 for (y = 0; y < BOARD_HEIGHT; y++) {
11142                     if (gameMode == IcsExamining) {
11143                         if (boards[currentMove][y][x] != EmptySquare) {
11144                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11145                                     AAA + x, ONE + y);
11146                             SendToICS(buf);
11147                         }
11148                     } else {
11149                         boards[0][y][x] = p;
11150                     }
11151                 }
11152             }
11153         }
11154         if (gameMode == EditPosition) {
11155             DrawPosition(FALSE, boards[0]);
11156         }
11157         break;
11158
11159       case WhitePlay:
11160         SetWhiteToPlayEvent();
11161         break;
11162
11163       case BlackPlay:
11164         SetBlackToPlayEvent();
11165         break;
11166
11167       case EmptySquare:
11168         if (gameMode == IcsExamining) {
11169             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11170             SendToICS(buf);
11171         } else {
11172             boards[0][y][x] = EmptySquare;
11173             DrawPosition(FALSE, boards[0]);
11174         }
11175         break;
11176
11177       case PromotePiece:
11178         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11179            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11180             selection = (ChessSquare) (PROMOTED piece);
11181         } else if(piece == EmptySquare) selection = WhiteSilver;
11182         else selection = (ChessSquare)((int)piece - 1);
11183         goto defaultlabel;
11184
11185       case DemotePiece:
11186         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11187            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11188             selection = (ChessSquare) (DEMOTED piece);
11189         } else if(piece == EmptySquare) selection = BlackSilver;
11190         else selection = (ChessSquare)((int)piece + 1);       
11191         goto defaultlabel;
11192
11193       case WhiteQueen:
11194       case BlackQueen:
11195         if(gameInfo.variant == VariantShatranj ||
11196            gameInfo.variant == VariantXiangqi  ||
11197            gameInfo.variant == VariantCourier    )
11198             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11199         goto defaultlabel;
11200
11201       case WhiteKing:
11202       case BlackKing:
11203         if(gameInfo.variant == VariantXiangqi)
11204             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11205         if(gameInfo.variant == VariantKnightmate)
11206             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11207       default:
11208         defaultlabel:
11209         if (gameMode == IcsExamining) {
11210             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11211                     PieceToChar(selection), AAA + x, ONE + y);
11212             SendToICS(buf);
11213         } else {
11214             boards[0][y][x] = selection;
11215             DrawPosition(FALSE, boards[0]);
11216         }
11217         break;
11218     }
11219 }
11220
11221
11222 void
11223 DropMenuEvent(selection, x, y)
11224      ChessSquare selection;
11225      int x, y;
11226 {
11227     ChessMove moveType;
11228
11229     switch (gameMode) {
11230       case IcsPlayingWhite:
11231       case MachinePlaysBlack:
11232         if (!WhiteOnMove(currentMove)) {
11233             DisplayMoveError(_("It is Black's turn"));
11234             return;
11235         }
11236         moveType = WhiteDrop;
11237         break;
11238       case IcsPlayingBlack:
11239       case MachinePlaysWhite:
11240         if (WhiteOnMove(currentMove)) {
11241             DisplayMoveError(_("It is White's turn"));
11242             return;
11243         }
11244         moveType = BlackDrop;
11245         break;
11246       case EditGame:
11247         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11248         break;
11249       default:
11250         return;
11251     }
11252
11253     if (moveType == BlackDrop && selection < BlackPawn) {
11254       selection = (ChessSquare) ((int) selection
11255                                  + (int) BlackPawn - (int) WhitePawn);
11256     }
11257     if (boards[currentMove][y][x] != EmptySquare) {
11258         DisplayMoveError(_("That square is occupied"));
11259         return;
11260     }
11261
11262     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11263 }
11264
11265 void
11266 AcceptEvent()
11267 {
11268     /* Accept a pending offer of any kind from opponent */
11269     
11270     if (appData.icsActive) {
11271         SendToICS(ics_prefix);
11272         SendToICS("accept\n");
11273     } else if (cmailMsgLoaded) {
11274         if (currentMove == cmailOldMove &&
11275             commentList[cmailOldMove] != NULL &&
11276             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11277                    "Black offers a draw" : "White offers a draw")) {
11278             TruncateGame();
11279             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11280             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11281         } else {
11282             DisplayError(_("There is no pending offer on this move"), 0);
11283             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11284         }
11285     } else {
11286         /* Not used for offers from chess program */
11287     }
11288 }
11289
11290 void
11291 DeclineEvent()
11292 {
11293     /* Decline a pending offer of any kind from opponent */
11294     
11295     if (appData.icsActive) {
11296         SendToICS(ics_prefix);
11297         SendToICS("decline\n");
11298     } else if (cmailMsgLoaded) {
11299         if (currentMove == cmailOldMove &&
11300             commentList[cmailOldMove] != NULL &&
11301             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11302                    "Black offers a draw" : "White offers a draw")) {
11303 #ifdef NOTDEF
11304             AppendComment(cmailOldMove, "Draw declined");
11305             DisplayComment(cmailOldMove - 1, "Draw declined");
11306 #endif /*NOTDEF*/
11307         } else {
11308             DisplayError(_("There is no pending offer on this move"), 0);
11309         }
11310     } else {
11311         /* Not used for offers from chess program */
11312     }
11313 }
11314
11315 void
11316 RematchEvent()
11317 {
11318     /* Issue ICS rematch command */
11319     if (appData.icsActive) {
11320         SendToICS(ics_prefix);
11321         SendToICS("rematch\n");
11322     }
11323 }
11324
11325 void
11326 CallFlagEvent()
11327 {
11328     /* Call your opponent's flag (claim a win on time) */
11329     if (appData.icsActive) {
11330         SendToICS(ics_prefix);
11331         SendToICS("flag\n");
11332     } else {
11333         switch (gameMode) {
11334           default:
11335             return;
11336           case MachinePlaysWhite:
11337             if (whiteFlag) {
11338                 if (blackFlag)
11339                   GameEnds(GameIsDrawn, "Both players ran out of time",
11340                            GE_PLAYER);
11341                 else
11342                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11343             } else {
11344                 DisplayError(_("Your opponent is not out of time"), 0);
11345             }
11346             break;
11347           case MachinePlaysBlack:
11348             if (blackFlag) {
11349                 if (whiteFlag)
11350                   GameEnds(GameIsDrawn, "Both players ran out of time",
11351                            GE_PLAYER);
11352                 else
11353                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11354             } else {
11355                 DisplayError(_("Your opponent is not out of time"), 0);
11356             }
11357             break;
11358         }
11359     }
11360 }
11361
11362 void
11363 DrawEvent()
11364 {
11365     /* Offer draw or accept pending draw offer from opponent */
11366     
11367     if (appData.icsActive) {
11368         /* Note: tournament rules require draw offers to be
11369            made after you make your move but before you punch
11370            your clock.  Currently ICS doesn't let you do that;
11371            instead, you immediately punch your clock after making
11372            a move, but you can offer a draw at any time. */
11373         
11374         SendToICS(ics_prefix);
11375         SendToICS("draw\n");
11376     } else if (cmailMsgLoaded) {
11377         if (currentMove == cmailOldMove &&
11378             commentList[cmailOldMove] != NULL &&
11379             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11380                    "Black offers a draw" : "White offers a draw")) {
11381             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11382             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11383         } else if (currentMove == cmailOldMove + 1) {
11384             char *offer = WhiteOnMove(cmailOldMove) ?
11385               "White offers a draw" : "Black offers a draw";
11386             AppendComment(currentMove, offer);
11387             DisplayComment(currentMove - 1, offer);
11388             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11389         } else {
11390             DisplayError(_("You must make your move before offering a draw"), 0);
11391             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11392         }
11393     } else if (first.offeredDraw) {
11394         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11395     } else {
11396         if (first.sendDrawOffers) {
11397             SendToProgram("draw\n", &first);
11398             userOfferedDraw = TRUE;
11399         }
11400     }
11401 }
11402
11403 void
11404 AdjournEvent()
11405 {
11406     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11407     
11408     if (appData.icsActive) {
11409         SendToICS(ics_prefix);
11410         SendToICS("adjourn\n");
11411     } else {
11412         /* Currently GNU Chess doesn't offer or accept Adjourns */
11413     }
11414 }
11415
11416
11417 void
11418 AbortEvent()
11419 {
11420     /* Offer Abort or accept pending Abort offer from opponent */
11421     
11422     if (appData.icsActive) {
11423         SendToICS(ics_prefix);
11424         SendToICS("abort\n");
11425     } else {
11426         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11427     }
11428 }
11429
11430 void
11431 ResignEvent()
11432 {
11433     /* Resign.  You can do this even if it's not your turn. */
11434     
11435     if (appData.icsActive) {
11436         SendToICS(ics_prefix);
11437         SendToICS("resign\n");
11438     } else {
11439         switch (gameMode) {
11440           case MachinePlaysWhite:
11441             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11442             break;
11443           case MachinePlaysBlack:
11444             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11445             break;
11446           case EditGame:
11447             if (cmailMsgLoaded) {
11448                 TruncateGame();
11449                 if (WhiteOnMove(cmailOldMove)) {
11450                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11451                 } else {
11452                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11453                 }
11454                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11455             }
11456             break;
11457           default:
11458             break;
11459         }
11460     }
11461 }
11462
11463
11464 void
11465 StopObservingEvent()
11466 {
11467     /* Stop observing current games */
11468     SendToICS(ics_prefix);
11469     SendToICS("unobserve\n");
11470 }
11471
11472 void
11473 StopExaminingEvent()
11474 {
11475     /* Stop observing current game */
11476     SendToICS(ics_prefix);
11477     SendToICS("unexamine\n");
11478 }
11479
11480 void
11481 ForwardInner(target)
11482      int target;
11483 {
11484     int limit;
11485
11486     if (appData.debugMode)
11487         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11488                 target, currentMove, forwardMostMove);
11489
11490     if (gameMode == EditPosition)
11491       return;
11492
11493     if (gameMode == PlayFromGameFile && !pausing)
11494       PauseEvent();
11495     
11496     if (gameMode == IcsExamining && pausing)
11497       limit = pauseExamForwardMostMove;
11498     else
11499       limit = forwardMostMove;
11500     
11501     if (target > limit) target = limit;
11502
11503     if (target > 0 && moveList[target - 1][0]) {
11504         int fromX, fromY, toX, toY;
11505         toX = moveList[target - 1][2] - AAA;
11506         toY = moveList[target - 1][3] - ONE;
11507         if (moveList[target - 1][1] == '@') {
11508             if (appData.highlightLastMove) {
11509                 SetHighlights(-1, -1, toX, toY);
11510             }
11511         } else {
11512             fromX = moveList[target - 1][0] - AAA;
11513             fromY = moveList[target - 1][1] - ONE;
11514             if (target == currentMove + 1) {
11515                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11516             }
11517             if (appData.highlightLastMove) {
11518                 SetHighlights(fromX, fromY, toX, toY);
11519             }
11520         }
11521     }
11522     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11523         gameMode == Training || gameMode == PlayFromGameFile || 
11524         gameMode == AnalyzeFile) {
11525         while (currentMove < target) {
11526             SendMoveToProgram(currentMove++, &first);
11527         }
11528     } else {
11529         currentMove = target;
11530     }
11531     
11532     if (gameMode == EditGame || gameMode == EndOfGame) {
11533         whiteTimeRemaining = timeRemaining[0][currentMove];
11534         blackTimeRemaining = timeRemaining[1][currentMove];
11535     }
11536     DisplayBothClocks();
11537     DisplayMove(currentMove - 1);
11538     DrawPosition(FALSE, boards[currentMove]);
11539     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11540     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11541         DisplayComment(currentMove - 1, commentList[currentMove]);
11542     }
11543 }
11544
11545
11546 void
11547 ForwardEvent()
11548 {
11549     if (gameMode == IcsExamining && !pausing) {
11550         SendToICS(ics_prefix);
11551         SendToICS("forward\n");
11552     } else {
11553         ForwardInner(currentMove + 1);
11554     }
11555 }
11556
11557 void
11558 ToEndEvent()
11559 {
11560     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11561         /* to optimze, we temporarily turn off analysis mode while we feed
11562          * the remaining moves to the engine. Otherwise we get analysis output
11563          * after each move.
11564          */ 
11565         if (first.analysisSupport) {
11566           SendToProgram("exit\nforce\n", &first);
11567           first.analyzing = FALSE;
11568         }
11569     }
11570         
11571     if (gameMode == IcsExamining && !pausing) {
11572         SendToICS(ics_prefix);
11573         SendToICS("forward 999999\n");
11574     } else {
11575         ForwardInner(forwardMostMove);
11576     }
11577
11578     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11579         /* we have fed all the moves, so reactivate analysis mode */
11580         SendToProgram("analyze\n", &first);
11581         first.analyzing = TRUE;
11582         /*first.maybeThinking = TRUE;*/
11583         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11584     }
11585 }
11586
11587 void
11588 BackwardInner(target)
11589      int target;
11590 {
11591     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11592
11593     if (appData.debugMode)
11594         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11595                 target, currentMove, forwardMostMove);
11596
11597     if (gameMode == EditPosition) return;
11598     if (currentMove <= backwardMostMove) {
11599         ClearHighlights();
11600         DrawPosition(full_redraw, boards[currentMove]);
11601         return;
11602     }
11603     if (gameMode == PlayFromGameFile && !pausing)
11604       PauseEvent();
11605     
11606     if (moveList[target][0]) {
11607         int fromX, fromY, toX, toY;
11608         toX = moveList[target][2] - AAA;
11609         toY = moveList[target][3] - ONE;
11610         if (moveList[target][1] == '@') {
11611             if (appData.highlightLastMove) {
11612                 SetHighlights(-1, -1, toX, toY);
11613             }
11614         } else {
11615             fromX = moveList[target][0] - AAA;
11616             fromY = moveList[target][1] - ONE;
11617             if (target == currentMove - 1) {
11618                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11619             }
11620             if (appData.highlightLastMove) {
11621                 SetHighlights(fromX, fromY, toX, toY);
11622             }
11623         }
11624     }
11625     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11626         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11627         while (currentMove > target) {
11628             SendToProgram("undo\n", &first);
11629             currentMove--;
11630         }
11631     } else {
11632         currentMove = target;
11633     }
11634     
11635     if (gameMode == EditGame || gameMode == EndOfGame) {
11636         whiteTimeRemaining = timeRemaining[0][currentMove];
11637         blackTimeRemaining = timeRemaining[1][currentMove];
11638     }
11639     DisplayBothClocks();
11640     DisplayMove(currentMove - 1);
11641     DrawPosition(full_redraw, boards[currentMove]);
11642     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11643     // [HGM] PV info: routine tests if comment empty
11644     DisplayComment(currentMove - 1, commentList[currentMove]);
11645 }
11646
11647 void
11648 BackwardEvent()
11649 {
11650     if (gameMode == IcsExamining && !pausing) {
11651         SendToICS(ics_prefix);
11652         SendToICS("backward\n");
11653     } else {
11654         BackwardInner(currentMove - 1);
11655     }
11656 }
11657
11658 void
11659 ToStartEvent()
11660 {
11661     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11662         /* to optimze, we temporarily turn off analysis mode while we undo
11663          * all the moves. Otherwise we get analysis output after each undo.
11664          */ 
11665         if (first.analysisSupport) {
11666           SendToProgram("exit\nforce\n", &first);
11667           first.analyzing = FALSE;
11668         }
11669     }
11670
11671     if (gameMode == IcsExamining && !pausing) {
11672         SendToICS(ics_prefix);
11673         SendToICS("backward 999999\n");
11674     } else {
11675         BackwardInner(backwardMostMove);
11676     }
11677
11678     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11679         /* we have fed all the moves, so reactivate analysis mode */
11680         SendToProgram("analyze\n", &first);
11681         first.analyzing = TRUE;
11682         /*first.maybeThinking = TRUE;*/
11683         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11684     }
11685 }
11686
11687 void
11688 ToNrEvent(int to)
11689 {
11690   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11691   if (to >= forwardMostMove) to = forwardMostMove;
11692   if (to <= backwardMostMove) to = backwardMostMove;
11693   if (to < currentMove) {
11694     BackwardInner(to);
11695   } else {
11696     ForwardInner(to);
11697   }
11698 }
11699
11700 void
11701 RevertEvent()
11702 {
11703     if (gameMode != IcsExamining) {
11704         DisplayError(_("You are not examining a game"), 0);
11705         return;
11706     }
11707     if (pausing) {
11708         DisplayError(_("You can't revert while pausing"), 0);
11709         return;
11710     }
11711     SendToICS(ics_prefix);
11712     SendToICS("revert\n");
11713 }
11714
11715 void
11716 RetractMoveEvent()
11717 {
11718     switch (gameMode) {
11719       case MachinePlaysWhite:
11720       case MachinePlaysBlack:
11721         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11722             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11723             return;
11724         }
11725         if (forwardMostMove < 2) return;
11726         currentMove = forwardMostMove = forwardMostMove - 2;
11727         whiteTimeRemaining = timeRemaining[0][currentMove];
11728         blackTimeRemaining = timeRemaining[1][currentMove];
11729         DisplayBothClocks();
11730         DisplayMove(currentMove - 1);
11731         ClearHighlights();/*!! could figure this out*/
11732         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11733         SendToProgram("remove\n", &first);
11734         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11735         break;
11736
11737       case BeginningOfGame:
11738       default:
11739         break;
11740
11741       case IcsPlayingWhite:
11742       case IcsPlayingBlack:
11743         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11744             SendToICS(ics_prefix);
11745             SendToICS("takeback 2\n");
11746         } else {
11747             SendToICS(ics_prefix);
11748             SendToICS("takeback 1\n");
11749         }
11750         break;
11751     }
11752 }
11753
11754 void
11755 MoveNowEvent()
11756 {
11757     ChessProgramState *cps;
11758
11759     switch (gameMode) {
11760       case MachinePlaysWhite:
11761         if (!WhiteOnMove(forwardMostMove)) {
11762             DisplayError(_("It is your turn"), 0);
11763             return;
11764         }
11765         cps = &first;
11766         break;
11767       case MachinePlaysBlack:
11768         if (WhiteOnMove(forwardMostMove)) {
11769             DisplayError(_("It is your turn"), 0);
11770             return;
11771         }
11772         cps = &first;
11773         break;
11774       case TwoMachinesPlay:
11775         if (WhiteOnMove(forwardMostMove) ==
11776             (first.twoMachinesColor[0] == 'w')) {
11777             cps = &first;
11778         } else {
11779             cps = &second;
11780         }
11781         break;
11782       case BeginningOfGame:
11783       default:
11784         return;
11785     }
11786     SendToProgram("?\n", cps);
11787 }
11788
11789 void
11790 TruncateGameEvent()
11791 {
11792     EditGameEvent();
11793     if (gameMode != EditGame) return;
11794     TruncateGame();
11795 }
11796
11797 void
11798 TruncateGame()
11799 {
11800     if (forwardMostMove > currentMove) {
11801         if (gameInfo.resultDetails != NULL) {
11802             free(gameInfo.resultDetails);
11803             gameInfo.resultDetails = NULL;
11804             gameInfo.result = GameUnfinished;
11805         }
11806         forwardMostMove = currentMove;
11807         HistorySet(parseList, backwardMostMove, forwardMostMove,
11808                    currentMove-1);
11809     }
11810 }
11811
11812 void
11813 HintEvent()
11814 {
11815     if (appData.noChessProgram) return;
11816     switch (gameMode) {
11817       case MachinePlaysWhite:
11818         if (WhiteOnMove(forwardMostMove)) {
11819             DisplayError(_("Wait until your turn"), 0);
11820             return;
11821         }
11822         break;
11823       case BeginningOfGame:
11824       case MachinePlaysBlack:
11825         if (!WhiteOnMove(forwardMostMove)) {
11826             DisplayError(_("Wait until your turn"), 0);
11827             return;
11828         }
11829         break;
11830       default:
11831         DisplayError(_("No hint available"), 0);
11832         return;
11833     }
11834     SendToProgram("hint\n", &first);
11835     hintRequested = TRUE;
11836 }
11837
11838 void
11839 BookEvent()
11840 {
11841     if (appData.noChessProgram) return;
11842     switch (gameMode) {
11843       case MachinePlaysWhite:
11844         if (WhiteOnMove(forwardMostMove)) {
11845             DisplayError(_("Wait until your turn"), 0);
11846             return;
11847         }
11848         break;
11849       case BeginningOfGame:
11850       case MachinePlaysBlack:
11851         if (!WhiteOnMove(forwardMostMove)) {
11852             DisplayError(_("Wait until your turn"), 0);
11853             return;
11854         }
11855         break;
11856       case EditPosition:
11857         EditPositionDone();
11858         break;
11859       case TwoMachinesPlay:
11860         return;
11861       default:
11862         break;
11863     }
11864     SendToProgram("bk\n", &first);
11865     bookOutput[0] = NULLCHAR;
11866     bookRequested = TRUE;
11867 }
11868
11869 void
11870 AboutGameEvent()
11871 {
11872     char *tags = PGNTags(&gameInfo);
11873     TagsPopUp(tags, CmailMsg());
11874     free(tags);
11875 }
11876
11877 /* end button procedures */
11878
11879 void
11880 PrintPosition(fp, move)
11881      FILE *fp;
11882      int move;
11883 {
11884     int i, j;
11885     
11886     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11887         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11888             char c = PieceToChar(boards[move][i][j]);
11889             fputc(c == 'x' ? '.' : c, fp);
11890             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11891         }
11892     }
11893     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11894       fprintf(fp, "white to play\n");
11895     else
11896       fprintf(fp, "black to play\n");
11897 }
11898
11899 void
11900 PrintOpponents(fp)
11901      FILE *fp;
11902 {
11903     if (gameInfo.white != NULL) {
11904         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11905     } else {
11906         fprintf(fp, "\n");
11907     }
11908 }
11909
11910 /* Find last component of program's own name, using some heuristics */
11911 void
11912 TidyProgramName(prog, host, buf)
11913      char *prog, *host, buf[MSG_SIZ];
11914 {
11915     char *p, *q;
11916     int local = (strcmp(host, "localhost") == 0);
11917     while (!local && (p = strchr(prog, ';')) != NULL) {
11918         p++;
11919         while (*p == ' ') p++;
11920         prog = p;
11921     }
11922     if (*prog == '"' || *prog == '\'') {
11923         q = strchr(prog + 1, *prog);
11924     } else {
11925         q = strchr(prog, ' ');
11926     }
11927     if (q == NULL) q = prog + strlen(prog);
11928     p = q;
11929     while (p >= prog && *p != '/' && *p != '\\') p--;
11930     p++;
11931     if(p == prog && *p == '"') p++;
11932     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11933     memcpy(buf, p, q - p);
11934     buf[q - p] = NULLCHAR;
11935     if (!local) {
11936         strcat(buf, "@");
11937         strcat(buf, host);
11938     }
11939 }
11940
11941 char *
11942 TimeControlTagValue()
11943 {
11944     char buf[MSG_SIZ];
11945     if (!appData.clockMode) {
11946         strcpy(buf, "-");
11947     } else if (movesPerSession > 0) {
11948         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11949     } else if (timeIncrement == 0) {
11950         sprintf(buf, "%ld", timeControl/1000);
11951     } else {
11952         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11953     }
11954     return StrSave(buf);
11955 }
11956
11957 void
11958 SetGameInfo()
11959 {
11960     /* This routine is used only for certain modes */
11961     VariantClass v = gameInfo.variant;
11962     ClearGameInfo(&gameInfo);
11963     gameInfo.variant = v;
11964
11965     switch (gameMode) {
11966       case MachinePlaysWhite:
11967         gameInfo.event = StrSave( appData.pgnEventHeader );
11968         gameInfo.site = StrSave(HostName());
11969         gameInfo.date = PGNDate();
11970         gameInfo.round = StrSave("-");
11971         gameInfo.white = StrSave(first.tidy);
11972         gameInfo.black = StrSave(UserName());
11973         gameInfo.timeControl = TimeControlTagValue();
11974         break;
11975
11976       case MachinePlaysBlack:
11977         gameInfo.event = StrSave( appData.pgnEventHeader );
11978         gameInfo.site = StrSave(HostName());
11979         gameInfo.date = PGNDate();
11980         gameInfo.round = StrSave("-");
11981         gameInfo.white = StrSave(UserName());
11982         gameInfo.black = StrSave(first.tidy);
11983         gameInfo.timeControl = TimeControlTagValue();
11984         break;
11985
11986       case TwoMachinesPlay:
11987         gameInfo.event = StrSave( appData.pgnEventHeader );
11988         gameInfo.site = StrSave(HostName());
11989         gameInfo.date = PGNDate();
11990         if (matchGame > 0) {
11991             char buf[MSG_SIZ];
11992             sprintf(buf, "%d", matchGame);
11993             gameInfo.round = StrSave(buf);
11994         } else {
11995             gameInfo.round = StrSave("-");
11996         }
11997         if (first.twoMachinesColor[0] == 'w') {
11998             gameInfo.white = StrSave(first.tidy);
11999             gameInfo.black = StrSave(second.tidy);
12000         } else {
12001             gameInfo.white = StrSave(second.tidy);
12002             gameInfo.black = StrSave(first.tidy);
12003         }
12004         gameInfo.timeControl = TimeControlTagValue();
12005         break;
12006
12007       case EditGame:
12008         gameInfo.event = StrSave("Edited game");
12009         gameInfo.site = StrSave(HostName());
12010         gameInfo.date = PGNDate();
12011         gameInfo.round = StrSave("-");
12012         gameInfo.white = StrSave("-");
12013         gameInfo.black = StrSave("-");
12014         break;
12015
12016       case EditPosition:
12017         gameInfo.event = StrSave("Edited position");
12018         gameInfo.site = StrSave(HostName());
12019         gameInfo.date = PGNDate();
12020         gameInfo.round = StrSave("-");
12021         gameInfo.white = StrSave("-");
12022         gameInfo.black = StrSave("-");
12023         break;
12024
12025       case IcsPlayingWhite:
12026       case IcsPlayingBlack:
12027       case IcsObserving:
12028       case IcsExamining:
12029         break;
12030
12031       case PlayFromGameFile:
12032         gameInfo.event = StrSave("Game from non-PGN file");
12033         gameInfo.site = StrSave(HostName());
12034         gameInfo.date = PGNDate();
12035         gameInfo.round = StrSave("-");
12036         gameInfo.white = StrSave("?");
12037         gameInfo.black = StrSave("?");
12038         break;
12039
12040       default:
12041         break;
12042     }
12043 }
12044
12045 void
12046 ReplaceComment(index, text)
12047      int index;
12048      char *text;
12049 {
12050     int len;
12051
12052     while (*text == '\n') text++;
12053     len = strlen(text);
12054     while (len > 0 && text[len - 1] == '\n') len--;
12055
12056     if (commentList[index] != NULL)
12057       free(commentList[index]);
12058
12059     if (len == 0) {
12060         commentList[index] = NULL;
12061         return;
12062     }
12063     commentList[index] = (char *) malloc(len + 2);
12064     strncpy(commentList[index], text, len);
12065     commentList[index][len] = '\n';
12066     commentList[index][len + 1] = NULLCHAR;
12067 }
12068
12069 void
12070 CrushCRs(text)
12071      char *text;
12072 {
12073   char *p = text;
12074   char *q = text;
12075   char ch;
12076
12077   do {
12078     ch = *p++;
12079     if (ch == '\r') continue;
12080     *q++ = ch;
12081   } while (ch != '\0');
12082 }
12083
12084 void
12085 AppendComment(index, text)
12086      int index;
12087      char *text;
12088 {
12089     int oldlen, len;
12090     char *old;
12091
12092     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12093
12094     CrushCRs(text);
12095     while (*text == '\n') text++;
12096     len = strlen(text);
12097     while (len > 0 && text[len - 1] == '\n') len--;
12098
12099     if (len == 0) return;
12100
12101     if (commentList[index] != NULL) {
12102         old = commentList[index];
12103         oldlen = strlen(old);
12104         commentList[index] = (char *) malloc(oldlen + len + 2);
12105         strcpy(commentList[index], old);
12106         free(old);
12107         strncpy(&commentList[index][oldlen], text, len);
12108         commentList[index][oldlen + len] = '\n';
12109         commentList[index][oldlen + len + 1] = NULLCHAR;
12110     } else {
12111         commentList[index] = (char *) malloc(len + 2);
12112         strncpy(commentList[index], text, len);
12113         commentList[index][len] = '\n';
12114         commentList[index][len + 1] = NULLCHAR;
12115     }
12116 }
12117
12118 static char * FindStr( char * text, char * sub_text )
12119 {
12120     char * result = strstr( text, sub_text );
12121
12122     if( result != NULL ) {
12123         result += strlen( sub_text );
12124     }
12125
12126     return result;
12127 }
12128
12129 /* [AS] Try to extract PV info from PGN comment */
12130 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12131 char *GetInfoFromComment( int index, char * text )
12132 {
12133     char * sep = text;
12134
12135     if( text != NULL && index > 0 ) {
12136         int score = 0;
12137         int depth = 0;
12138         int time = -1, sec = 0, deci;
12139         char * s_eval = FindStr( text, "[%eval " );
12140         char * s_emt = FindStr( text, "[%emt " );
12141
12142         if( s_eval != NULL || s_emt != NULL ) {
12143             /* New style */
12144             char delim;
12145
12146             if( s_eval != NULL ) {
12147                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12148                     return text;
12149                 }
12150
12151                 if( delim != ']' ) {
12152                     return text;
12153                 }
12154             }
12155
12156             if( s_emt != NULL ) {
12157             }
12158         }
12159         else {
12160             /* We expect something like: [+|-]nnn.nn/dd */
12161             int score_lo = 0;
12162
12163             sep = strchr( text, '/' );
12164             if( sep == NULL || sep < (text+4) ) {
12165                 return text;
12166             }
12167
12168             time = -1; sec = -1; deci = -1;
12169             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12170                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12171                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12172                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12173                 return text;
12174             }
12175
12176             if( score_lo < 0 || score_lo >= 100 ) {
12177                 return text;
12178             }
12179
12180             if(sec >= 0) time = 600*time + 10*sec; else
12181             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12182
12183             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12184
12185             /* [HGM] PV time: now locate end of PV info */
12186             while( *++sep >= '0' && *sep <= '9'); // strip depth
12187             if(time >= 0)
12188             while( *++sep >= '0' && *sep <= '9'); // strip time
12189             if(sec >= 0)
12190             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12191             if(deci >= 0)
12192             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12193             while(*sep == ' ') sep++;
12194         }
12195
12196         if( depth <= 0 ) {
12197             return text;
12198         }
12199
12200         if( time < 0 ) {
12201             time = -1;
12202         }
12203
12204         pvInfoList[index-1].depth = depth;
12205         pvInfoList[index-1].score = score;
12206         pvInfoList[index-1].time  = 10*time; // centi-sec
12207     }
12208     return sep;
12209 }
12210
12211 void
12212 SendToProgram(message, cps)
12213      char *message;
12214      ChessProgramState *cps;
12215 {
12216     int count, outCount, error;
12217     char buf[MSG_SIZ];
12218
12219     if (cps->pr == NULL) return;
12220     Attention(cps);
12221     
12222     if (appData.debugMode) {
12223         TimeMark now;
12224         GetTimeMark(&now);
12225         fprintf(debugFP, "%ld >%-6s: %s", 
12226                 SubtractTimeMarks(&now, &programStartTime),
12227                 cps->which, message);
12228     }
12229     
12230     count = strlen(message);
12231     outCount = OutputToProcess(cps->pr, message, count, &error);
12232     if (outCount < count && !exiting 
12233                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12234         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12235         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12236             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12237                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12238                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12239             } else {
12240                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12241             }
12242             gameInfo.resultDetails = buf;
12243         }
12244         DisplayFatalError(buf, error, 1);
12245     }
12246 }
12247
12248 void
12249 ReceiveFromProgram(isr, closure, message, count, error)
12250      InputSourceRef isr;
12251      VOIDSTAR closure;
12252      char *message;
12253      int count;
12254      int error;
12255 {
12256     char *end_str;
12257     char buf[MSG_SIZ];
12258     ChessProgramState *cps = (ChessProgramState *)closure;
12259
12260     if (isr != cps->isr) return; /* Killed intentionally */
12261     if (count <= 0) {
12262         if (count == 0) {
12263             sprintf(buf,
12264                     _("Error: %s chess program (%s) exited unexpectedly"),
12265                     cps->which, cps->program);
12266         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12267                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12268                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12269                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12270                 } else {
12271                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12272                 }
12273                 gameInfo.resultDetails = buf;
12274             }
12275             RemoveInputSource(cps->isr);
12276             DisplayFatalError(buf, 0, 1);
12277         } else {
12278             sprintf(buf,
12279                     _("Error reading from %s chess program (%s)"),
12280                     cps->which, cps->program);
12281             RemoveInputSource(cps->isr);
12282
12283             /* [AS] Program is misbehaving badly... kill it */
12284             if( count == -2 ) {
12285                 DestroyChildProcess( cps->pr, 9 );
12286                 cps->pr = NoProc;
12287             }
12288
12289             DisplayFatalError(buf, error, 1);
12290         }
12291         return;
12292     }
12293     
12294     if ((end_str = strchr(message, '\r')) != NULL)
12295       *end_str = NULLCHAR;
12296     if ((end_str = strchr(message, '\n')) != NULL)
12297       *end_str = NULLCHAR;
12298     
12299     if (appData.debugMode) {
12300         TimeMark now; int print = 1;
12301         char *quote = ""; char c; int i;
12302
12303         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12304                 char start = message[0];
12305                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12306                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12307                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12308                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12309                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12310                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12311                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12312                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12313                         { quote = "# "; print = (appData.engineComments == 2); }
12314                 message[0] = start; // restore original message
12315         }
12316         if(print) {
12317                 GetTimeMark(&now);
12318                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12319                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12320                         quote,
12321                         message);
12322         }
12323     }
12324
12325     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12326     if (appData.icsEngineAnalyze) {
12327         if (strstr(message, "whisper") != NULL ||
12328              strstr(message, "kibitz") != NULL || 
12329             strstr(message, "tellics") != NULL) return;
12330     }
12331
12332     HandleMachineMove(message, cps);
12333 }
12334
12335
12336 void
12337 SendTimeControl(cps, mps, tc, inc, sd, st)
12338      ChessProgramState *cps;
12339      int mps, inc, sd, st;
12340      long tc;
12341 {
12342     char buf[MSG_SIZ];
12343     int seconds;
12344
12345     if( timeControl_2 > 0 ) {
12346         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12347             tc = timeControl_2;
12348         }
12349     }
12350     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12351     inc /= cps->timeOdds;
12352     st  /= cps->timeOdds;
12353
12354     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12355
12356     if (st > 0) {
12357       /* Set exact time per move, normally using st command */
12358       if (cps->stKludge) {
12359         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12360         seconds = st % 60;
12361         if (seconds == 0) {
12362           sprintf(buf, "level 1 %d\n", st/60);
12363         } else {
12364           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12365         }
12366       } else {
12367         sprintf(buf, "st %d\n", st);
12368       }
12369     } else {
12370       /* Set conventional or incremental time control, using level command */
12371       if (seconds == 0) {
12372         /* Note old gnuchess bug -- minutes:seconds used to not work.
12373            Fixed in later versions, but still avoid :seconds
12374            when seconds is 0. */
12375         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12376       } else {
12377         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12378                 seconds, inc/1000);
12379       }
12380     }
12381     SendToProgram(buf, cps);
12382
12383     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12384     /* Orthogonally, limit search to given depth */
12385     if (sd > 0) {
12386       if (cps->sdKludge) {
12387         sprintf(buf, "depth\n%d\n", sd);
12388       } else {
12389         sprintf(buf, "sd %d\n", sd);
12390       }
12391       SendToProgram(buf, cps);
12392     }
12393
12394     if(cps->nps > 0) { /* [HGM] nps */
12395         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12396         else {
12397                 sprintf(buf, "nps %d\n", cps->nps);
12398               SendToProgram(buf, cps);
12399         }
12400     }
12401 }
12402
12403 ChessProgramState *WhitePlayer()
12404 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12405 {
12406     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12407        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12408         return &second;
12409     return &first;
12410 }
12411
12412 void
12413 SendTimeRemaining(cps, machineWhite)
12414      ChessProgramState *cps;
12415      int /*boolean*/ machineWhite;
12416 {
12417     char message[MSG_SIZ];
12418     long time, otime;
12419
12420     /* Note: this routine must be called when the clocks are stopped
12421        or when they have *just* been set or switched; otherwise
12422        it will be off by the time since the current tick started.
12423     */
12424     if (machineWhite) {
12425         time = whiteTimeRemaining / 10;
12426         otime = blackTimeRemaining / 10;
12427     } else {
12428         time = blackTimeRemaining / 10;
12429         otime = whiteTimeRemaining / 10;
12430     }
12431     /* [HGM] translate opponent's time by time-odds factor */
12432     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12433     if (appData.debugMode) {
12434         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12435     }
12436
12437     if (time <= 0) time = 1;
12438     if (otime <= 0) otime = 1;
12439     
12440     sprintf(message, "time %ld\n", time);
12441     SendToProgram(message, cps);
12442
12443     sprintf(message, "otim %ld\n", otime);
12444     SendToProgram(message, cps);
12445 }
12446
12447 int
12448 BoolFeature(p, name, loc, cps)
12449      char **p;
12450      char *name;
12451      int *loc;
12452      ChessProgramState *cps;
12453 {
12454   char buf[MSG_SIZ];
12455   int len = strlen(name);
12456   int val;
12457   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12458     (*p) += len + 1;
12459     sscanf(*p, "%d", &val);
12460     *loc = (val != 0);
12461     while (**p && **p != ' ') (*p)++;
12462     sprintf(buf, "accepted %s\n", name);
12463     SendToProgram(buf, cps);
12464     return TRUE;
12465   }
12466   return FALSE;
12467 }
12468
12469 int
12470 IntFeature(p, name, loc, cps)
12471      char **p;
12472      char *name;
12473      int *loc;
12474      ChessProgramState *cps;
12475 {
12476   char buf[MSG_SIZ];
12477   int len = strlen(name);
12478   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12479     (*p) += len + 1;
12480     sscanf(*p, "%d", loc);
12481     while (**p && **p != ' ') (*p)++;
12482     sprintf(buf, "accepted %s\n", name);
12483     SendToProgram(buf, cps);
12484     return TRUE;
12485   }
12486   return FALSE;
12487 }
12488
12489 int
12490 StringFeature(p, name, loc, cps)
12491      char **p;
12492      char *name;
12493      char loc[];
12494      ChessProgramState *cps;
12495 {
12496   char buf[MSG_SIZ];
12497   int len = strlen(name);
12498   if (strncmp((*p), name, len) == 0
12499       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12500     (*p) += len + 2;
12501     sscanf(*p, "%[^\"]", loc);
12502     while (**p && **p != '\"') (*p)++;
12503     if (**p == '\"') (*p)++;
12504     sprintf(buf, "accepted %s\n", name);
12505     SendToProgram(buf, cps);
12506     return TRUE;
12507   }
12508   return FALSE;
12509 }
12510
12511 int 
12512 ParseOption(Option *opt, ChessProgramState *cps)
12513 // [HGM] options: process the string that defines an engine option, and determine
12514 // name, type, default value, and allowed value range
12515 {
12516         char *p, *q, buf[MSG_SIZ];
12517         int n, min = (-1)<<31, max = 1<<31, def;
12518
12519         if(p = strstr(opt->name, " -spin ")) {
12520             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12521             if(max < min) max = min; // enforce consistency
12522             if(def < min) def = min;
12523             if(def > max) def = max;
12524             opt->value = def;
12525             opt->min = min;
12526             opt->max = max;
12527             opt->type = Spin;
12528         } else if((p = strstr(opt->name, " -slider "))) {
12529             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12530             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12531             if(max < min) max = min; // enforce consistency
12532             if(def < min) def = min;
12533             if(def > max) def = max;
12534             opt->value = def;
12535             opt->min = min;
12536             opt->max = max;
12537             opt->type = Spin; // Slider;
12538         } else if((p = strstr(opt->name, " -string "))) {
12539             opt->textValue = p+9;
12540             opt->type = TextBox;
12541         } else if((p = strstr(opt->name, " -file "))) {
12542             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12543             opt->textValue = p+7;
12544             opt->type = TextBox; // FileName;
12545         } else if((p = strstr(opt->name, " -path "))) {
12546             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12547             opt->textValue = p+7;
12548             opt->type = TextBox; // PathName;
12549         } else if(p = strstr(opt->name, " -check ")) {
12550             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12551             opt->value = (def != 0);
12552             opt->type = CheckBox;
12553         } else if(p = strstr(opt->name, " -combo ")) {
12554             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12555             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12556             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12557             opt->value = n = 0;
12558             while(q = StrStr(q, " /// ")) {
12559                 n++; *q = 0;    // count choices, and null-terminate each of them
12560                 q += 5;
12561                 if(*q == '*') { // remember default, which is marked with * prefix
12562                     q++;
12563                     opt->value = n;
12564                 }
12565                 cps->comboList[cps->comboCnt++] = q;
12566             }
12567             cps->comboList[cps->comboCnt++] = NULL;
12568             opt->max = n + 1;
12569             opt->type = ComboBox;
12570         } else if(p = strstr(opt->name, " -button")) {
12571             opt->type = Button;
12572         } else if(p = strstr(opt->name, " -save")) {
12573             opt->type = SaveButton;
12574         } else return FALSE;
12575         *p = 0; // terminate option name
12576         // now look if the command-line options define a setting for this engine option.
12577         if(cps->optionSettings && cps->optionSettings[0])
12578             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12579         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12580                 sprintf(buf, "option %s", p);
12581                 if(p = strstr(buf, ",")) *p = 0;
12582                 strcat(buf, "\n");
12583                 SendToProgram(buf, cps);
12584         }
12585         return TRUE;
12586 }
12587
12588 void
12589 FeatureDone(cps, val)
12590      ChessProgramState* cps;
12591      int val;
12592 {
12593   DelayedEventCallback cb = GetDelayedEvent();
12594   if ((cb == InitBackEnd3 && cps == &first) ||
12595       (cb == TwoMachinesEventIfReady && cps == &second)) {
12596     CancelDelayedEvent();
12597     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12598   }
12599   cps->initDone = val;
12600 }
12601
12602 /* Parse feature command from engine */
12603 void
12604 ParseFeatures(args, cps)
12605      char* args;
12606      ChessProgramState *cps;  
12607 {
12608   char *p = args;
12609   char *q;
12610   int val;
12611   char buf[MSG_SIZ];
12612
12613   for (;;) {
12614     while (*p == ' ') p++;
12615     if (*p == NULLCHAR) return;
12616
12617     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12618     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12619     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12620     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12621     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12622     if (BoolFeature(&p, "reuse", &val, cps)) {
12623       /* Engine can disable reuse, but can't enable it if user said no */
12624       if (!val) cps->reuse = FALSE;
12625       continue;
12626     }
12627     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12628     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12629       if (gameMode == TwoMachinesPlay) {
12630         DisplayTwoMachinesTitle();
12631       } else {
12632         DisplayTitle("");
12633       }
12634       continue;
12635     }
12636     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12637     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12638     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12639     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12640     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12641     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12642     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12643     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12644     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12645     if (IntFeature(&p, "done", &val, cps)) {
12646       FeatureDone(cps, val);
12647       continue;
12648     }
12649     /* Added by Tord: */
12650     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12651     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12652     /* End of additions by Tord */
12653
12654     /* [HGM] added features: */
12655     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12656     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12657     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12658     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12659     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12660     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12661     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12662         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12663             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12664             SendToProgram(buf, cps);
12665             continue;
12666         }
12667         if(cps->nrOptions >= MAX_OPTIONS) {
12668             cps->nrOptions--;
12669             sprintf(buf, "%s engine has too many options\n", cps->which);
12670             DisplayError(buf, 0);
12671         }
12672         continue;
12673     }
12674     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12675     /* End of additions by HGM */
12676
12677     /* unknown feature: complain and skip */
12678     q = p;
12679     while (*q && *q != '=') q++;
12680     sprintf(buf, "rejected %.*s\n", q-p, p);
12681     SendToProgram(buf, cps);
12682     p = q;
12683     if (*p == '=') {
12684       p++;
12685       if (*p == '\"') {
12686         p++;
12687         while (*p && *p != '\"') p++;
12688         if (*p == '\"') p++;
12689       } else {
12690         while (*p && *p != ' ') p++;
12691       }
12692     }
12693   }
12694
12695 }
12696
12697 void
12698 PeriodicUpdatesEvent(newState)
12699      int newState;
12700 {
12701     if (newState == appData.periodicUpdates)
12702       return;
12703
12704     appData.periodicUpdates=newState;
12705
12706     /* Display type changes, so update it now */
12707     DisplayAnalysis();
12708
12709     /* Get the ball rolling again... */
12710     if (newState) {
12711         AnalysisPeriodicEvent(1);
12712         StartAnalysisClock();
12713     }
12714 }
12715
12716 void
12717 PonderNextMoveEvent(newState)
12718      int newState;
12719 {
12720     if (newState == appData.ponderNextMove) return;
12721     if (gameMode == EditPosition) EditPositionDone();
12722     if (newState) {
12723         SendToProgram("hard\n", &first);
12724         if (gameMode == TwoMachinesPlay) {
12725             SendToProgram("hard\n", &second);
12726         }
12727     } else {
12728         SendToProgram("easy\n", &first);
12729         thinkOutput[0] = NULLCHAR;
12730         if (gameMode == TwoMachinesPlay) {
12731             SendToProgram("easy\n", &second);
12732         }
12733     }
12734     appData.ponderNextMove = newState;
12735 }
12736
12737 void
12738 NewSettingEvent(option, command, value)
12739      char *command;
12740      int option, value;
12741 {
12742     char buf[MSG_SIZ];
12743
12744     if (gameMode == EditPosition) EditPositionDone();
12745     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12746     SendToProgram(buf, &first);
12747     if (gameMode == TwoMachinesPlay) {
12748         SendToProgram(buf, &second);
12749     }
12750 }
12751
12752 void
12753 ShowThinkingEvent()
12754 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12755 {
12756     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12757     int newState = appData.showThinking
12758         // [HGM] thinking: other features now need thinking output as well
12759         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12760     
12761     if (oldState == newState) return;
12762     oldState = newState;
12763     if (gameMode == EditPosition) EditPositionDone();
12764     if (oldState) {
12765         SendToProgram("post\n", &first);
12766         if (gameMode == TwoMachinesPlay) {
12767             SendToProgram("post\n", &second);
12768         }
12769     } else {
12770         SendToProgram("nopost\n", &first);
12771         thinkOutput[0] = NULLCHAR;
12772         if (gameMode == TwoMachinesPlay) {
12773             SendToProgram("nopost\n", &second);
12774         }
12775     }
12776 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12777 }
12778
12779 void
12780 AskQuestionEvent(title, question, replyPrefix, which)
12781      char *title; char *question; char *replyPrefix; char *which;
12782 {
12783   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12784   if (pr == NoProc) return;
12785   AskQuestion(title, question, replyPrefix, pr);
12786 }
12787
12788 void
12789 DisplayMove(moveNumber)
12790      int moveNumber;
12791 {
12792     char message[MSG_SIZ];
12793     char res[MSG_SIZ];
12794     char cpThinkOutput[MSG_SIZ];
12795
12796     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12797     
12798     if (moveNumber == forwardMostMove - 1 || 
12799         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12800
12801         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12802
12803         if (strchr(cpThinkOutput, '\n')) {
12804             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12805         }
12806     } else {
12807         *cpThinkOutput = NULLCHAR;
12808     }
12809
12810     /* [AS] Hide thinking from human user */
12811     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12812         *cpThinkOutput = NULLCHAR;
12813         if( thinkOutput[0] != NULLCHAR ) {
12814             int i;
12815
12816             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12817                 cpThinkOutput[i] = '.';
12818             }
12819             cpThinkOutput[i] = NULLCHAR;
12820             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12821         }
12822     }
12823
12824     if (moveNumber == forwardMostMove - 1 &&
12825         gameInfo.resultDetails != NULL) {
12826         if (gameInfo.resultDetails[0] == NULLCHAR) {
12827             sprintf(res, " %s", PGNResult(gameInfo.result));
12828         } else {
12829             sprintf(res, " {%s} %s",
12830                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12831         }
12832     } else {
12833         res[0] = NULLCHAR;
12834     }
12835
12836     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12837         DisplayMessage(res, cpThinkOutput);
12838     } else {
12839         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12840                 WhiteOnMove(moveNumber) ? " " : ".. ",
12841                 parseList[moveNumber], res);
12842         DisplayMessage(message, cpThinkOutput);
12843     }
12844 }
12845
12846 void
12847 DisplayAnalysisText(text)
12848      char *text;
12849 {
12850     char buf[MSG_SIZ];
12851
12852     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile 
12853                || appData.icsEngineAnalyze) {
12854         sprintf(buf, "Analysis (%s)", first.tidy);
12855         AnalysisPopUp(buf, text);
12856     }
12857 }
12858
12859 static int
12860 only_one_move(str)
12861      char *str;
12862 {
12863     while (*str && isspace(*str)) ++str;
12864     while (*str && !isspace(*str)) ++str;
12865     if (!*str) return 1;
12866     while (*str && isspace(*str)) ++str;
12867     if (!*str) return 1;
12868     return 0;
12869 }
12870
12871 void
12872 DisplayAnalysis()
12873 {
12874     char buf[MSG_SIZ];
12875     char lst[MSG_SIZ / 2];
12876     double nps;
12877     static char *xtra[] = { "", " (--)", " (++)" };
12878     int h, m, s, cs;
12879   
12880     if (programStats.time == 0) {
12881         programStats.time = 1;
12882     }
12883   
12884     if (programStats.got_only_move) {
12885         safeStrCpy(buf, programStats.movelist, sizeof(buf));
12886     } else {
12887         safeStrCpy( lst, programStats.movelist, sizeof(lst));
12888
12889         nps = (u64ToDouble(programStats.nodes) /
12890              ((double)programStats.time /100.0));
12891
12892         cs = programStats.time % 100;
12893         s = programStats.time / 100;
12894         h = (s / (60*60));
12895         s = s - h*60*60;
12896         m = (s/60);
12897         s = s - m*60;
12898
12899         if (programStats.moves_left > 0 && appData.periodicUpdates) {
12900           if (programStats.move_name[0] != NULLCHAR) {
12901             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12902                     programStats.depth,
12903                     programStats.nr_moves-programStats.moves_left,
12904                     programStats.nr_moves, programStats.move_name,
12905                     ((float)programStats.score)/100.0, lst,
12906                     only_one_move(lst)?
12907                     xtra[programStats.got_fail] : "",
12908                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12909           } else {
12910             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12911                     programStats.depth,
12912                     programStats.nr_moves-programStats.moves_left,
12913                     programStats.nr_moves, ((float)programStats.score)/100.0,
12914                     lst,
12915                     only_one_move(lst)?
12916                     xtra[programStats.got_fail] : "",
12917                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12918           }
12919         } else {
12920             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12921                     programStats.depth,
12922                     ((float)programStats.score)/100.0,
12923                     lst,
12924                     only_one_move(lst)?
12925                     xtra[programStats.got_fail] : "",
12926                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12927         }
12928     }
12929     DisplayAnalysisText(buf);
12930 }
12931
12932 void
12933 DisplayComment(moveNumber, text)
12934      int moveNumber;
12935      char *text;
12936 {
12937     char title[MSG_SIZ];
12938     char buf[8000]; // comment can be long!
12939     int score, depth;
12940
12941     if( appData.autoDisplayComment ) {
12942         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12943             strcpy(title, "Comment");
12944         } else {
12945             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12946                     WhiteOnMove(moveNumber) ? " " : ".. ",
12947                     parseList[moveNumber]);
12948         }
12949         // [HGM] PV info: display PV info together with (or as) comment
12950         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12951             if(text == NULL) text = "";                                           
12952             score = pvInfoList[moveNumber].score;
12953             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12954                               depth, (pvInfoList[moveNumber].time+50)/100, text);
12955             text = buf;
12956         }
12957     } else title[0] = 0;
12958
12959     if (text != NULL)
12960         CommentPopUp(title, text);
12961 }
12962
12963 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12964  * might be busy thinking or pondering.  It can be omitted if your
12965  * gnuchess is configured to stop thinking immediately on any user
12966  * input.  However, that gnuchess feature depends on the FIONREAD
12967  * ioctl, which does not work properly on some flavors of Unix.
12968  */
12969 void
12970 Attention(cps)
12971      ChessProgramState *cps;
12972 {
12973 #if ATTENTION
12974     if (!cps->useSigint) return;
12975     if (appData.noChessProgram || (cps->pr == NoProc)) return;
12976     switch (gameMode) {
12977       case MachinePlaysWhite:
12978       case MachinePlaysBlack:
12979       case TwoMachinesPlay:
12980       case IcsPlayingWhite:
12981       case IcsPlayingBlack:
12982       case AnalyzeMode:
12983       case AnalyzeFile:
12984         /* Skip if we know it isn't thinking */
12985         if (!cps->maybeThinking) return;
12986         if (appData.debugMode)
12987           fprintf(debugFP, "Interrupting %s\n", cps->which);
12988         InterruptChildProcess(cps->pr);
12989         cps->maybeThinking = FALSE;
12990         break;
12991       default:
12992         break;
12993     }
12994 #endif /*ATTENTION*/
12995 }
12996
12997 int
12998 CheckFlags()
12999 {
13000     if (whiteTimeRemaining <= 0) {
13001         if (!whiteFlag) {
13002             whiteFlag = TRUE;
13003             if (appData.icsActive) {
13004                 if (appData.autoCallFlag &&
13005                     gameMode == IcsPlayingBlack && !blackFlag) {
13006                   SendToICS(ics_prefix);
13007                   SendToICS("flag\n");
13008                 }
13009             } else {
13010                 if (blackFlag) {
13011                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13012                 } else {
13013                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13014                     if (appData.autoCallFlag) {
13015                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13016                         return TRUE;
13017                     }
13018                 }
13019             }
13020         }
13021     }
13022     if (blackTimeRemaining <= 0) {
13023         if (!blackFlag) {
13024             blackFlag = TRUE;
13025             if (appData.icsActive) {
13026                 if (appData.autoCallFlag &&
13027                     gameMode == IcsPlayingWhite && !whiteFlag) {
13028                   SendToICS(ics_prefix);
13029                   SendToICS("flag\n");
13030                 }
13031             } else {
13032                 if (whiteFlag) {
13033                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13034                 } else {
13035                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13036                     if (appData.autoCallFlag) {
13037                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13038                         return TRUE;
13039                     }
13040                 }
13041             }
13042         }
13043     }
13044     return FALSE;
13045 }
13046
13047 void
13048 CheckTimeControl()
13049 {
13050     if (!appData.clockMode || appData.icsActive ||
13051         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13052
13053     /*
13054      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13055      */
13056     if ( !WhiteOnMove(forwardMostMove) )
13057         /* White made time control */
13058         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13059         /* [HGM] time odds: correct new time quota for time odds! */
13060                                             / WhitePlayer()->timeOdds;
13061       else
13062         /* Black made time control */
13063         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13064                                             / WhitePlayer()->other->timeOdds;
13065 }
13066
13067 void
13068 DisplayBothClocks()
13069 {
13070     int wom = gameMode == EditPosition ?
13071       !blackPlaysFirst : WhiteOnMove(currentMove);
13072     DisplayWhiteClock(whiteTimeRemaining, wom);
13073     DisplayBlackClock(blackTimeRemaining, !wom);
13074 }
13075
13076
13077 /* Timekeeping seems to be a portability nightmare.  I think everyone
13078    has ftime(), but I'm really not sure, so I'm including some ifdefs
13079    to use other calls if you don't.  Clocks will be less accurate if
13080    you have neither ftime nor gettimeofday.
13081 */
13082
13083 /* VS 2008 requires the #include outside of the function */
13084 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13085 #include <sys/timeb.h>
13086 #endif
13087
13088 /* Get the current time as a TimeMark */
13089 void
13090 GetTimeMark(tm)
13091      TimeMark *tm;
13092 {
13093 #if HAVE_GETTIMEOFDAY
13094
13095     struct timeval timeVal;
13096     struct timezone timeZone;
13097
13098     gettimeofday(&timeVal, &timeZone);
13099     tm->sec = (long) timeVal.tv_sec; 
13100     tm->ms = (int) (timeVal.tv_usec / 1000L);
13101
13102 #else /*!HAVE_GETTIMEOFDAY*/
13103 #if HAVE_FTIME
13104
13105 // include <sys/timeb.h> / moved to just above start of function
13106     struct timeb timeB;
13107
13108     ftime(&timeB);
13109     tm->sec = (long) timeB.time;
13110     tm->ms = (int) timeB.millitm;
13111
13112 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13113     tm->sec = (long) time(NULL);
13114     tm->ms = 0;
13115 #endif
13116 #endif
13117 }
13118
13119 /* Return the difference in milliseconds between two
13120    time marks.  We assume the difference will fit in a long!
13121 */
13122 long
13123 SubtractTimeMarks(tm2, tm1)
13124      TimeMark *tm2, *tm1;
13125 {
13126     return 1000L*(tm2->sec - tm1->sec) +
13127            (long) (tm2->ms - tm1->ms);
13128 }
13129
13130
13131 /*
13132  * Code to manage the game clocks.
13133  *
13134  * In tournament play, black starts the clock and then white makes a move.
13135  * We give the human user a slight advantage if he is playing white---the
13136  * clocks don't run until he makes his first move, so it takes zero time.
13137  * Also, we don't account for network lag, so we could get out of sync
13138  * with GNU Chess's clock -- but then, referees are always right.  
13139  */
13140
13141 static TimeMark tickStartTM;
13142 static long intendedTickLength;
13143
13144 long
13145 NextTickLength(timeRemaining)
13146      long timeRemaining;
13147 {
13148     long nominalTickLength, nextTickLength;
13149
13150     if (timeRemaining > 0L && timeRemaining <= 10000L)
13151       nominalTickLength = 100L;
13152     else
13153       nominalTickLength = 1000L;
13154     nextTickLength = timeRemaining % nominalTickLength;
13155     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13156
13157     return nextTickLength;
13158 }
13159
13160 /* Adjust clock one minute up or down */
13161 void
13162 AdjustClock(Boolean which, int dir)
13163 {
13164     if(which) blackTimeRemaining += 60000*dir;
13165     else      whiteTimeRemaining += 60000*dir;
13166     DisplayBothClocks();
13167 }
13168
13169 /* Stop clocks and reset to a fresh time control */
13170 void
13171 ResetClocks() 
13172 {
13173     (void) StopClockTimer();
13174     if (appData.icsActive) {
13175         whiteTimeRemaining = blackTimeRemaining = 0;
13176     } else { /* [HGM] correct new time quote for time odds */
13177         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13178         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13179     }
13180     if (whiteFlag || blackFlag) {
13181         DisplayTitle("");
13182         whiteFlag = blackFlag = FALSE;
13183     }
13184     DisplayBothClocks();
13185 }
13186
13187 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13188
13189 /* Decrement running clock by amount of time that has passed */
13190 void
13191 DecrementClocks()
13192 {
13193     long timeRemaining;
13194     long lastTickLength, fudge;
13195     TimeMark now;
13196
13197     if (!appData.clockMode) return;
13198     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13199         
13200     GetTimeMark(&now);
13201
13202     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13203
13204     /* Fudge if we woke up a little too soon */
13205     fudge = intendedTickLength - lastTickLength;
13206     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13207
13208     if (WhiteOnMove(forwardMostMove)) {
13209         if(whiteNPS >= 0) lastTickLength = 0;
13210         timeRemaining = whiteTimeRemaining -= lastTickLength;
13211         DisplayWhiteClock(whiteTimeRemaining - fudge,
13212                           WhiteOnMove(currentMove));
13213     } else {
13214         if(blackNPS >= 0) lastTickLength = 0;
13215         timeRemaining = blackTimeRemaining -= lastTickLength;
13216         DisplayBlackClock(blackTimeRemaining - fudge,
13217                           !WhiteOnMove(currentMove));
13218     }
13219
13220     if (CheckFlags()) return;
13221         
13222     tickStartTM = now;
13223     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13224     StartClockTimer(intendedTickLength);
13225
13226     /* if the time remaining has fallen below the alarm threshold, sound the
13227      * alarm. if the alarm has sounded and (due to a takeback or time control
13228      * with increment) the time remaining has increased to a level above the
13229      * threshold, reset the alarm so it can sound again. 
13230      */
13231     
13232     if (appData.icsActive && appData.icsAlarm) {
13233
13234         /* make sure we are dealing with the user's clock */
13235         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13236                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13237            )) return;
13238
13239         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13240             alarmSounded = FALSE;
13241         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13242             PlayAlarmSound();
13243             alarmSounded = TRUE;
13244         }
13245     }
13246 }
13247
13248
13249 /* A player has just moved, so stop the previously running
13250    clock and (if in clock mode) start the other one.
13251    We redisplay both clocks in case we're in ICS mode, because
13252    ICS gives us an update to both clocks after every move.
13253    Note that this routine is called *after* forwardMostMove
13254    is updated, so the last fractional tick must be subtracted
13255    from the color that is *not* on move now.
13256 */
13257 void
13258 SwitchClocks()
13259 {
13260     long lastTickLength;
13261     TimeMark now;
13262     int flagged = FALSE;
13263
13264     GetTimeMark(&now);
13265
13266     if (StopClockTimer() && appData.clockMode) {
13267         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13268         if (WhiteOnMove(forwardMostMove)) {
13269             if(blackNPS >= 0) lastTickLength = 0;
13270             blackTimeRemaining -= lastTickLength;
13271            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13272 //         if(pvInfoList[forwardMostMove-1].time == -1)
13273                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13274                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13275         } else {
13276            if(whiteNPS >= 0) lastTickLength = 0;
13277            whiteTimeRemaining -= lastTickLength;
13278            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13279 //         if(pvInfoList[forwardMostMove-1].time == -1)
13280                  pvInfoList[forwardMostMove-1].time = 
13281                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13282         }
13283         flagged = CheckFlags();
13284     }
13285     CheckTimeControl();
13286
13287     if (flagged || !appData.clockMode) return;
13288
13289     switch (gameMode) {
13290       case MachinePlaysBlack:
13291       case MachinePlaysWhite:
13292       case BeginningOfGame:
13293         if (pausing) return;
13294         break;
13295
13296       case EditGame:
13297       case PlayFromGameFile:
13298       case IcsExamining:
13299         return;
13300
13301       default:
13302         break;
13303     }
13304
13305     tickStartTM = now;
13306     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13307       whiteTimeRemaining : blackTimeRemaining);
13308     StartClockTimer(intendedTickLength);
13309 }
13310         
13311
13312 /* Stop both clocks */
13313 void
13314 StopClocks()
13315 {       
13316     long lastTickLength;
13317     TimeMark now;
13318
13319     if (!StopClockTimer()) return;
13320     if (!appData.clockMode) return;
13321
13322     GetTimeMark(&now);
13323
13324     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13325     if (WhiteOnMove(forwardMostMove)) {
13326         if(whiteNPS >= 0) lastTickLength = 0;
13327         whiteTimeRemaining -= lastTickLength;
13328         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13329     } else {
13330         if(blackNPS >= 0) lastTickLength = 0;
13331         blackTimeRemaining -= lastTickLength;
13332         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13333     }
13334     CheckFlags();
13335 }
13336         
13337 /* Start clock of player on move.  Time may have been reset, so
13338    if clock is already running, stop and restart it. */
13339 void
13340 StartClocks()
13341 {
13342     (void) StopClockTimer(); /* in case it was running already */
13343     DisplayBothClocks();
13344     if (CheckFlags()) return;
13345
13346     if (!appData.clockMode) return;
13347     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13348
13349     GetTimeMark(&tickStartTM);
13350     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13351       whiteTimeRemaining : blackTimeRemaining);
13352
13353    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13354     whiteNPS = blackNPS = -1; 
13355     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13356        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13357         whiteNPS = first.nps;
13358     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13359        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13360         blackNPS = first.nps;
13361     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13362         whiteNPS = second.nps;
13363     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13364         blackNPS = second.nps;
13365     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13366
13367     StartClockTimer(intendedTickLength);
13368 }
13369
13370 char *
13371 TimeString(ms)
13372      long ms;
13373 {
13374     long second, minute, hour, day;
13375     char *sign = "";
13376     static char buf[32];
13377     
13378     if (ms > 0 && ms <= 9900) {
13379       /* convert milliseconds to tenths, rounding up */
13380       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13381
13382       sprintf(buf, " %03.1f ", tenths/10.0);
13383       return buf;
13384     }
13385
13386     /* convert milliseconds to seconds, rounding up */
13387     /* use floating point to avoid strangeness of integer division
13388        with negative dividends on many machines */
13389     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13390
13391     if (second < 0) {
13392         sign = "-";
13393         second = -second;
13394     }
13395     
13396     day = second / (60 * 60 * 24);
13397     second = second % (60 * 60 * 24);
13398     hour = second / (60 * 60);
13399     second = second % (60 * 60);
13400     minute = second / 60;
13401     second = second % 60;
13402     
13403     if (day > 0)
13404       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13405               sign, day, hour, minute, second);
13406     else if (hour > 0)
13407       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13408     else
13409       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13410     
13411     return buf;
13412 }
13413
13414
13415 /*
13416  * This is necessary because some C libraries aren't ANSI C compliant yet.
13417  */
13418 char *
13419 StrStr(string, match)
13420      char *string, *match;
13421 {
13422     int i, length;
13423     
13424     length = strlen(match);
13425     
13426     for (i = strlen(string) - length; i >= 0; i--, string++)
13427       if (!strncmp(match, string, length))
13428         return string;
13429     
13430     return NULL;
13431 }
13432
13433 char *
13434 StrCaseStr(string, match)
13435      char *string, *match;
13436 {
13437     int i, j, length;
13438     
13439     length = strlen(match);
13440     
13441     for (i = strlen(string) - length; i >= 0; i--, string++) {
13442         for (j = 0; j < length; j++) {
13443             if (ToLower(match[j]) != ToLower(string[j]))
13444               break;
13445         }
13446         if (j == length) return string;
13447     }
13448
13449     return NULL;
13450 }
13451
13452 #ifndef _amigados
13453 int
13454 StrCaseCmp(s1, s2)
13455      char *s1, *s2;
13456 {
13457     char c1, c2;
13458     
13459     for (;;) {
13460         c1 = ToLower(*s1++);
13461         c2 = ToLower(*s2++);
13462         if (c1 > c2) return 1;
13463         if (c1 < c2) return -1;
13464         if (c1 == NULLCHAR) return 0;
13465     }
13466 }
13467
13468
13469 int
13470 ToLower(c)
13471      int c;
13472 {
13473     return isupper(c) ? tolower(c) : c;
13474 }
13475
13476
13477 int
13478 ToUpper(c)
13479      int c;
13480 {
13481     return islower(c) ? toupper(c) : c;
13482 }
13483 #endif /* !_amigados    */
13484
13485 char *
13486 StrSave(s)
13487      char *s;
13488 {
13489     char *ret;
13490
13491     if ((ret = (char *) malloc(strlen(s) + 1))) {
13492         strcpy(ret, s);
13493     }
13494     return ret;
13495 }
13496
13497 char *
13498 StrSavePtr(s, savePtr)
13499      char *s, **savePtr;
13500 {
13501     if (*savePtr) {
13502         free(*savePtr);
13503     }
13504     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13505         strcpy(*savePtr, s);
13506     }
13507     return(*savePtr);
13508 }
13509
13510 char *
13511 PGNDate()
13512 {
13513     time_t clock;
13514     struct tm *tm;
13515     char buf[MSG_SIZ];
13516
13517     clock = time((time_t *)NULL);
13518     tm = localtime(&clock);
13519     sprintf(buf, "%04d.%02d.%02d",
13520             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13521     return StrSave(buf);
13522 }
13523
13524
13525 char *
13526 PositionToFEN(move, overrideCastling)
13527      int move;
13528      char *overrideCastling;
13529 {
13530     int i, j, fromX, fromY, toX, toY;
13531     int whiteToPlay;
13532     char buf[128];
13533     char *p, *q;
13534     int emptycount;
13535     ChessSquare piece;
13536
13537     whiteToPlay = (gameMode == EditPosition) ?
13538       !blackPlaysFirst : (move % 2 == 0);
13539     p = buf;
13540
13541     /* Piece placement data */
13542     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13543         emptycount = 0;
13544         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13545             if (boards[move][i][j] == EmptySquare) {
13546                 emptycount++;
13547             } else { ChessSquare piece = boards[move][i][j];
13548                 if (emptycount > 0) {
13549                     if(emptycount<10) /* [HGM] can be >= 10 */
13550                         *p++ = '0' + emptycount;
13551                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13552                     emptycount = 0;
13553                 }
13554                 if(PieceToChar(piece) == '+') {
13555                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13556                     *p++ = '+';
13557                     piece = (ChessSquare)(DEMOTED piece);
13558                 } 
13559                 *p++ = PieceToChar(piece);
13560                 if(p[-1] == '~') {
13561                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13562                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13563                     *p++ = '~';
13564                 }
13565             }
13566         }
13567         if (emptycount > 0) {
13568             if(emptycount<10) /* [HGM] can be >= 10 */
13569                 *p++ = '0' + emptycount;
13570             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13571             emptycount = 0;
13572         }
13573         *p++ = '/';
13574     }
13575     *(p - 1) = ' ';
13576
13577     /* [HGM] print Crazyhouse or Shogi holdings */
13578     if( gameInfo.holdingsWidth ) {
13579         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13580         q = p;
13581         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13582             piece = boards[move][i][BOARD_WIDTH-1];
13583             if( piece != EmptySquare )
13584               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13585                   *p++ = PieceToChar(piece);
13586         }
13587         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13588             piece = boards[move][BOARD_HEIGHT-i-1][0];
13589             if( piece != EmptySquare )
13590               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13591                   *p++ = PieceToChar(piece);
13592         }
13593
13594         if( q == p ) *p++ = '-';
13595         *p++ = ']';
13596         *p++ = ' ';
13597     }
13598
13599     /* Active color */
13600     *p++ = whiteToPlay ? 'w' : 'b';
13601     *p++ = ' ';
13602
13603   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13604     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13605   } else {
13606   if(nrCastlingRights) {
13607      q = p;
13608      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13609        /* [HGM] write directly from rights */
13610            if(castlingRights[move][2] >= 0 &&
13611               castlingRights[move][0] >= 0   )
13612                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13613            if(castlingRights[move][2] >= 0 &&
13614               castlingRights[move][1] >= 0   )
13615                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13616            if(castlingRights[move][5] >= 0 &&
13617               castlingRights[move][3] >= 0   )
13618                 *p++ = castlingRights[move][3] + AAA;
13619            if(castlingRights[move][5] >= 0 &&
13620               castlingRights[move][4] >= 0   )
13621                 *p++ = castlingRights[move][4] + AAA;
13622      } else {
13623
13624         /* [HGM] write true castling rights */
13625         if( nrCastlingRights == 6 ) {
13626             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13627                castlingRights[move][2] >= 0  ) *p++ = 'K';
13628             if(castlingRights[move][1] == BOARD_LEFT &&
13629                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13630             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13631                castlingRights[move][5] >= 0  ) *p++ = 'k';
13632             if(castlingRights[move][4] == BOARD_LEFT &&
13633                castlingRights[move][5] >= 0  ) *p++ = 'q';
13634         }
13635      }
13636      if (q == p) *p++ = '-'; /* No castling rights */
13637      *p++ = ' ';
13638   }
13639
13640   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13641      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13642     /* En passant target square */
13643     if (move > backwardMostMove) {
13644         fromX = moveList[move - 1][0] - AAA;
13645         fromY = moveList[move - 1][1] - ONE;
13646         toX = moveList[move - 1][2] - AAA;
13647         toY = moveList[move - 1][3] - ONE;
13648         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13649             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13650             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13651             fromX == toX) {
13652             /* 2-square pawn move just happened */
13653             *p++ = toX + AAA;
13654             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13655         } else {
13656             *p++ = '-';
13657         }
13658     } else {
13659         *p++ = '-';
13660     }
13661     *p++ = ' ';
13662   }
13663   }
13664
13665     /* [HGM] find reversible plies */
13666     {   int i = 0, j=move;
13667
13668         if (appData.debugMode) { int k;
13669             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13670             for(k=backwardMostMove; k<=forwardMostMove; k++)
13671                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13672
13673         }
13674
13675         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13676         if( j == backwardMostMove ) i += initialRulePlies;
13677         sprintf(p, "%d ", i);
13678         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13679     }
13680     /* Fullmove number */
13681     sprintf(p, "%d", (move / 2) + 1);
13682     
13683     return StrSave(buf);
13684 }
13685
13686 Boolean
13687 ParseFEN(board, blackPlaysFirst, fen)
13688     Board board;
13689      int *blackPlaysFirst;
13690      char *fen;
13691 {
13692     int i, j;
13693     char *p;
13694     int emptycount;
13695     ChessSquare piece;
13696
13697     p = fen;
13698
13699     /* [HGM] by default clear Crazyhouse holdings, if present */
13700     if(gameInfo.holdingsWidth) {
13701        for(i=0; i<BOARD_HEIGHT; i++) {
13702            board[i][0]             = EmptySquare; /* black holdings */
13703            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13704            board[i][1]             = (ChessSquare) 0; /* black counts */
13705            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13706        }
13707     }
13708
13709     /* Piece placement data */
13710     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13711         j = 0;
13712         for (;;) {
13713             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13714                 if (*p == '/') p++;
13715                 emptycount = gameInfo.boardWidth - j;
13716                 while (emptycount--)
13717                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13718                 break;
13719 #if(BOARD_SIZE >= 10)
13720             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13721                 p++; emptycount=10;
13722                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13723                 while (emptycount--)
13724                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13725 #endif
13726             } else if (isdigit(*p)) {
13727                 emptycount = *p++ - '0';
13728                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13729                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13730                 while (emptycount--)
13731                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13732             } else if (*p == '+' || isalpha(*p)) {
13733                 if (j >= gameInfo.boardWidth) return FALSE;
13734                 if(*p=='+') {
13735                     piece = CharToPiece(*++p);
13736                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13737                     piece = (ChessSquare) (PROMOTED piece ); p++;
13738                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13739                 } else piece = CharToPiece(*p++);
13740
13741                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13742                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13743                     piece = (ChessSquare) (PROMOTED piece);
13744                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13745                     p++;
13746                 }
13747                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13748             } else {
13749                 return FALSE;
13750             }
13751         }
13752     }
13753     while (*p == '/' || *p == ' ') p++;
13754
13755     /* [HGM] look for Crazyhouse holdings here */
13756     while(*p==' ') p++;
13757     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13758         if(*p == '[') p++;
13759         if(*p == '-' ) *p++; /* empty holdings */ else {
13760             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13761             /* if we would allow FEN reading to set board size, we would   */
13762             /* have to add holdings and shift the board read so far here   */
13763             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13764                 *p++;
13765                 if((int) piece >= (int) BlackPawn ) {
13766                     i = (int)piece - (int)BlackPawn;
13767                     i = PieceToNumber((ChessSquare)i);
13768                     if( i >= gameInfo.holdingsSize ) return FALSE;
13769                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13770                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13771                 } else {
13772                     i = (int)piece - (int)WhitePawn;
13773                     i = PieceToNumber((ChessSquare)i);
13774                     if( i >= gameInfo.holdingsSize ) return FALSE;
13775                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13776                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13777                 }
13778             }
13779         }
13780         if(*p == ']') *p++;
13781     }
13782
13783     while(*p == ' ') p++;
13784
13785     /* Active color */
13786     switch (*p++) {
13787       case 'w':
13788         *blackPlaysFirst = FALSE;
13789         break;
13790       case 'b': 
13791         *blackPlaysFirst = TRUE;
13792         break;
13793       default:
13794         return FALSE;
13795     }
13796
13797     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13798     /* return the extra info in global variiables             */
13799
13800     /* set defaults in case FEN is incomplete */
13801     FENepStatus = EP_UNKNOWN;
13802     for(i=0; i<nrCastlingRights; i++ ) {
13803         FENcastlingRights[i] =
13804             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13805     }   /* assume possible unless obviously impossible */
13806     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13807     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13808     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13809     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13810     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13811     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13812     FENrulePlies = 0;
13813
13814     while(*p==' ') p++;
13815     if(nrCastlingRights) {
13816       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13817           /* castling indicator present, so default becomes no castlings */
13818           for(i=0; i<nrCastlingRights; i++ ) {
13819                  FENcastlingRights[i] = -1;
13820           }
13821       }
13822       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13823              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13824              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13825              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13826         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13827
13828         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13829             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13830             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13831         }
13832         switch(c) {
13833           case'K':
13834               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13835               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13836               FENcastlingRights[2] = whiteKingFile;
13837               break;
13838           case'Q':
13839               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13840               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13841               FENcastlingRights[2] = whiteKingFile;
13842               break;
13843           case'k':
13844               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13845               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13846               FENcastlingRights[5] = blackKingFile;
13847               break;
13848           case'q':
13849               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13850               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13851               FENcastlingRights[5] = blackKingFile;
13852           case '-':
13853               break;
13854           default: /* FRC castlings */
13855               if(c >= 'a') { /* black rights */
13856                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13857                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13858                   if(i == BOARD_RGHT) break;
13859                   FENcastlingRights[5] = i;
13860                   c -= AAA;
13861                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13862                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13863                   if(c > i)
13864                       FENcastlingRights[3] = c;
13865                   else
13866                       FENcastlingRights[4] = c;
13867               } else { /* white rights */
13868                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13869                     if(board[0][i] == WhiteKing) break;
13870                   if(i == BOARD_RGHT) break;
13871                   FENcastlingRights[2] = i;
13872                   c -= AAA - 'a' + 'A';
13873                   if(board[0][c] >= WhiteKing) break;
13874                   if(c > i)
13875                       FENcastlingRights[0] = c;
13876                   else
13877                       FENcastlingRights[1] = c;
13878               }
13879         }
13880       }
13881     if (appData.debugMode) {
13882         fprintf(debugFP, "FEN castling rights:");
13883         for(i=0; i<nrCastlingRights; i++)
13884         fprintf(debugFP, " %d", FENcastlingRights[i]);
13885         fprintf(debugFP, "\n");
13886     }
13887
13888       while(*p==' ') p++;
13889     }
13890
13891     /* read e.p. field in games that know e.p. capture */
13892     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13893        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13894       if(*p=='-') {
13895         p++; FENepStatus = EP_NONE;
13896       } else {
13897          char c = *p++ - AAA;
13898
13899          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13900          if(*p >= '0' && *p <='9') *p++;
13901          FENepStatus = c;
13902       }
13903     }
13904
13905
13906     if(sscanf(p, "%d", &i) == 1) {
13907         FENrulePlies = i; /* 50-move ply counter */
13908         /* (The move number is still ignored)    */
13909     }
13910
13911     return TRUE;
13912 }
13913       
13914 void
13915 EditPositionPasteFEN(char *fen)
13916 {
13917   if (fen != NULL) {
13918     Board initial_position;
13919
13920     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13921       DisplayError(_("Bad FEN position in clipboard"), 0);
13922       return ;
13923     } else {
13924       int savedBlackPlaysFirst = blackPlaysFirst;
13925       EditPositionEvent();
13926       blackPlaysFirst = savedBlackPlaysFirst;
13927       CopyBoard(boards[0], initial_position);
13928           /* [HGM] copy FEN attributes as well */
13929           {   int i;
13930               initialRulePlies = FENrulePlies;
13931               epStatus[0] = FENepStatus;
13932               for( i=0; i<nrCastlingRights; i++ )
13933                   castlingRights[0][i] = FENcastlingRights[i];
13934           }
13935       EditPositionDone();
13936       DisplayBothClocks();
13937       DrawPosition(FALSE, boards[currentMove]);
13938     }
13939   }
13940 }