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