wrong default value for engineDebugOutput
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 #else /* not STDC_HEADERS */
81 # if HAVE_STRING_H
82 #  include <string.h>
83 # else /* not HAVE_STRING_H */
84 #  include <strings.h>
85 # endif /* not HAVE_STRING_H */
86 #endif /* not STDC_HEADERS */
87
88 #if HAVE_SYS_FCNTL_H
89 # include <sys/fcntl.h>
90 #else /* not HAVE_SYS_FCNTL_H */
91 # if HAVE_FCNTL_H
92 #  include <fcntl.h>
93 # endif /* HAVE_FCNTL_H */
94 #endif /* not HAVE_SYS_FCNTL_H */
95
96 #if TIME_WITH_SYS_TIME
97 # include <sys/time.h>
98 # include <time.h>
99 #else
100 # if HAVE_SYS_TIME_H
101 #  include <sys/time.h>
102 # else
103 #  include <time.h>
104 # endif
105 #endif
106
107 #if defined(_amigados) && !defined(__GNUC__)
108 struct timezone {
109     int tz_minuteswest;
110     int tz_dsttime;
111 };
112 extern int gettimeofday(struct timeval *, struct timezone *);
113 #endif
114
115 #if HAVE_UNISTD_H
116 # include <unistd.h>
117 #endif
118
119 #include "common.h"
120 #include "frontend.h"
121 #include "backend.h"
122 #include "parser.h"
123 #include "moves.h"
124 #if ZIPPY
125 # include "zippy.h"
126 #endif
127 #include "backendz.h"
128 #include "gettext.h" 
129  
130 #ifdef ENABLE_NLS 
131 # define _(s) gettext (s) 
132 # define N_(s) gettext_noop (s) 
133 #else 
134 # define _(s) (s) 
135 # define N_(s) s 
136 #endif 
137
138
139 /* A point in time */
140 typedef struct {
141     long sec;  /* Assuming this is >= 32 bits */
142     int ms;    /* Assuming this is >= 16 bits */
143 } TimeMark;
144
145 int establish P((void));
146 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
147                          char *buf, int count, int error));
148 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
149                       char *buf, int count, int error));
150 void SendToICS P((char *s));
151 void SendToICSDelayed P((char *s, long msdelay));
152 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
153                       int toX, int toY));
154 void HandleMachineMove P((char *message, ChessProgramState *cps));
155 int AutoPlayOneMove P((void));
156 int LoadGameOneMove P((ChessMove readAhead));
157 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
158 int LoadPositionFromFile P((char *filename, int n, char *title));
159 int SavePositionToFile P((char *filename));
160 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
161                   Board board, char *castle, char *ep));
162 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
163 void ShowMove P((int fromX, int fromY, int toX, int toY));
164 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
165                    /*char*/int promoChar));
166 void BackwardInner P((int target));
167 void ForwardInner P((int target));
168 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
169 void EditPositionDone P((void));
170 void PrintOpponents P((FILE *fp));
171 void PrintPosition P((FILE *fp, int move));
172 void StartChessProgram P((ChessProgramState *cps));
173 void SendToProgram P((char *message, ChessProgramState *cps));
174 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
175 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
176                            char *buf, int count, int error));
177 void SendTimeControl P((ChessProgramState *cps,
178                         int mps, long tc, int inc, int sd, int st));
179 char *TimeControlTagValue P((void));
180 void Attention P((ChessProgramState *cps));
181 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
182 void ResurrectChessProgram P((void));
183 void DisplayComment P((int moveNumber, char *text));
184 void DisplayMove P((int moveNumber));
185 void DisplayAnalysis P((void));
186
187 void ParseGameHistory P((char *game));
188 void ParseBoard12 P((char *string));
189 void StartClocks P((void));
190 void SwitchClocks P((void));
191 void StopClocks P((void));
192 void ResetClocks P((void));
193 char *PGNDate P((void));
194 void SetGameInfo P((void));
195 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
196 int RegisterMove P((void));
197 void MakeRegisteredMove P((void));
198 void TruncateGame P((void));
199 int looking_at P((char *, int *, char *));
200 void CopyPlayerNameIntoFileName P((char **, char *));
201 char *SavePart P((char *));
202 int SaveGameOldStyle P((FILE *));
203 int SaveGamePGN P((FILE *));
204 void GetTimeMark P((TimeMark *));
205 long SubtractTimeMarks P((TimeMark *, TimeMark *));
206 int CheckFlags P((void));
207 long NextTickLength P((long));
208 void CheckTimeControl P((void));
209 void show_bytes P((FILE *, char *, int));
210 int string_to_rating P((char *str));
211 void ParseFeatures P((char* args, ChessProgramState *cps));
212 void InitBackEnd3 P((void));
213 void FeatureDone P((ChessProgramState* cps, int val));
214 void InitChessProgram P((ChessProgramState *cps, int setup));
215 void OutputKibitz(int window, char *text);
216 int PerpetualChase(int first, int last);
217 int EngineOutputIsUp();
218 void InitDrawingSizes(int x, int y);
219
220 #ifdef WIN32
221        extern void ConsoleCreate();
222 #endif
223
224 ChessProgramState *WhitePlayer();
225 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
226 int VerifyDisplayMode P(());
227
228 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
229 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
230 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
231 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
232 extern char installDir[MSG_SIZ];
233
234 extern int tinyLayout, smallLayout;
235 ChessProgramStats programStats;
236 static int exiting = 0; /* [HGM] moved to top */
237 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
238 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
239 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
240 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
241 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
242 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
243 int opponentKibitzes;
244 int lastSavedGame; /* [HGM] save: ID of game */
245 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
246 extern int chatCount;
247 int chattingPartner;
248
249 /* States for ics_getting_history */
250 #define H_FALSE 0
251 #define H_REQUESTED 1
252 #define H_GOT_REQ_HEADER 2
253 #define H_GOT_UNREQ_HEADER 3
254 #define H_GETTING_MOVES 4
255 #define H_GOT_UNWANTED_HEADER 5
256
257 /* whosays values for GameEnds */
258 #define GE_ICS 0
259 #define GE_ENGINE 1
260 #define GE_PLAYER 2
261 #define GE_FILE 3
262 #define GE_XBOARD 4
263 #define GE_ENGINE1 5
264 #define GE_ENGINE2 6
265
266 /* Maximum number of games in a cmail message */
267 #define CMAIL_MAX_GAMES 20
268
269 /* Different types of move when calling RegisterMove */
270 #define CMAIL_MOVE   0
271 #define CMAIL_RESIGN 1
272 #define CMAIL_DRAW   2
273 #define CMAIL_ACCEPT 3
274
275 /* Different types of result to remember for each game */
276 #define CMAIL_NOT_RESULT 0
277 #define CMAIL_OLD_RESULT 1
278 #define CMAIL_NEW_RESULT 2
279
280 /* Telnet protocol constants */
281 #define TN_WILL 0373
282 #define TN_WONT 0374
283 #define TN_DO   0375
284 #define TN_DONT 0376
285 #define TN_IAC  0377
286 #define TN_ECHO 0001
287 #define TN_SGA  0003
288 #define TN_PORT 23
289
290 /* [AS] */
291 static char * safeStrCpy( char * dst, const char * src, size_t count )
292 {
293     assert( dst != NULL );
294     assert( src != NULL );
295     assert( count > 0 );
296
297     strncpy( dst, src, count );
298     dst[ count-1 ] = '\0';
299     return dst;
300 }
301
302 #if 0
303 //[HGM] for future use? Conditioned out for now to suppress warning.
304 static char * safeStrCat( char * dst, const char * src, size_t count )
305 {
306     size_t  dst_len;
307
308     assert( dst != NULL );
309     assert( src != NULL );
310     assert( count > 0 );
311
312     dst_len = strlen(dst);
313
314     assert( count > dst_len ); /* Buffer size must be greater than current length */
315
316     safeStrCpy( dst + dst_len, src, count - dst_len );
317
318     return dst;
319 }
320 #endif
321
322 /* Some compiler can't cast u64 to double
323  * This function do the job for us:
324
325  * We use the highest bit for cast, this only
326  * works if the highest bit is not
327  * in use (This should not happen)
328  *
329  * We used this for all compiler
330  */
331 double
332 u64ToDouble(u64 value)
333 {
334   double r;
335   u64 tmp = value & u64Const(0x7fffffffffffffff);
336   r = (double)(s64)tmp;
337   if (value & u64Const(0x8000000000000000))
338        r +=  9.2233720368547758080e18; /* 2^63 */
339  return r;
340 }
341
342 /* Fake up flags for now, as we aren't keeping track of castling
343    availability yet. [HGM] Change of logic: the flag now only
344    indicates the type of castlings allowed by the rule of the game.
345    The actual rights themselves are maintained in the array
346    castlingRights, as part of the game history, and are not probed
347    by this function.
348  */
349 int
350 PosFlags(index)
351 {
352   int flags = F_ALL_CASTLE_OK;
353   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
354   switch (gameInfo.variant) {
355   case VariantSuicide:
356     flags &= ~F_ALL_CASTLE_OK;
357   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
358     flags |= F_IGNORE_CHECK;
359   case VariantLosers:
360     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
361     break;
362   case VariantAtomic:
363     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
364     break;
365   case VariantKriegspiel:
366     flags |= F_KRIEGSPIEL_CAPTURE;
367     break;
368   case VariantCapaRandom: 
369   case VariantFischeRandom:
370     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
371   case VariantNoCastle:
372   case VariantShatranj:
373   case VariantCourier:
374     flags &= ~F_ALL_CASTLE_OK;
375     break;
376   default:
377     break;
378   }
379   return flags;
380 }
381
382 FILE *gameFileFP, *debugFP;
383
384 /* 
385     [AS] Note: sometimes, the sscanf() function is used to parse the input
386     into a fixed-size buffer. Because of this, we must be prepared to
387     receive strings as long as the size of the input buffer, which is currently
388     set to 4K for Windows and 8K for the rest.
389     So, we must either allocate sufficiently large buffers here, or
390     reduce the size of the input buffer in the input reading part.
391 */
392
393 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
394 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
395 char thinkOutput1[MSG_SIZ*10];
396
397 ChessProgramState first, second;
398
399 /* premove variables */
400 int premoveToX = 0;
401 int premoveToY = 0;
402 int premoveFromX = 0;
403 int premoveFromY = 0;
404 int premovePromoChar = 0;
405 int gotPremove = 0;
406 Boolean alarmSounded;
407 /* end premove variables */
408
409 char *ics_prefix = "$";
410 int ics_type = ICS_GENERIC;
411
412 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
413 int pauseExamForwardMostMove = 0;
414 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
415 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
416 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
417 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
418 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
419 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
420 int whiteFlag = FALSE, blackFlag = FALSE;
421 int userOfferedDraw = FALSE;
422 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
423 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
424 int cmailMoveType[CMAIL_MAX_GAMES];
425 long ics_clock_paused = 0;
426 ProcRef icsPR = NoProc, cmailPR = NoProc;
427 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
428 GameMode gameMode = BeginningOfGame;
429 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
430 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
431 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
432 int hiddenThinkOutputState = 0; /* [AS] */
433 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
434 int adjudicateLossPlies = 6;
435 char white_holding[64], black_holding[64];
436 TimeMark lastNodeCountTime;
437 long lastNodeCount=0;
438 int have_sent_ICS_logon = 0;
439 int movesPerSession;
440 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
441 long timeControl_2; /* [AS] Allow separate time controls */
442 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
443 long timeRemaining[2][MAX_MOVES];
444 int matchGame = 0;
445 TimeMark programStartTime;
446 char ics_handle[MSG_SIZ];
447 int have_set_title = 0;
448
449 /* animateTraining preserves the state of appData.animate
450  * when Training mode is activated. This allows the
451  * response to be animated when appData.animate == TRUE and
452  * appData.animateDragging == TRUE.
453  */
454 Boolean animateTraining;
455
456 GameInfo gameInfo;
457
458 AppData appData;
459
460 Board boards[MAX_MOVES];
461 /* [HGM] Following 7 needed for accurate legality tests: */
462 signed char  epStatus[MAX_MOVES];
463 signed char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
464 signed char  castlingRank[BOARD_SIZE]; // and corresponding ranks
465 signed char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
466 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
467 int   initialRulePlies, FENrulePlies;
468 char  FENepStatus;
469 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
470 int loadFlag = 0; 
471 int shuffleOpenings;
472 int mute; // mute all sounds
473
474 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
475     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
476         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
477     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
478         BlackKing, BlackBishop, BlackKnight, BlackRook }
479 };
480
481 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
482     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
483         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
484     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
485         BlackKing, BlackKing, BlackKnight, BlackRook }
486 };
487
488 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
489     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
490         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
491     { BlackRook, BlackMan, BlackBishop, BlackQueen,
492         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
493 };
494
495 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
496     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
497         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
498     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
499         BlackKing, BlackBishop, BlackKnight, BlackRook }
500 };
501
502 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
503     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
504         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
505     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
506         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
507 };
508
509
510 #if (BOARD_SIZE>=10)
511 ChessSquare ShogiArray[2][BOARD_SIZE] = {
512     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
513         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
514     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
515         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
516 };
517
518 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
519     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
520         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
521     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
522         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
523 };
524
525 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
526     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
527         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
528     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
529         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
530 };
531
532 ChessSquare GreatArray[2][BOARD_SIZE] = {
533     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
534         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
535     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
536         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
537 };
538
539 ChessSquare JanusArray[2][BOARD_SIZE] = {
540     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
541         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
542     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
543         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
544 };
545
546 #ifdef GOTHIC
547 ChessSquare GothicArray[2][BOARD_SIZE] = {
548     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
549         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
550     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
551         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
552 };
553 #else // !GOTHIC
554 #define GothicArray CapablancaArray
555 #endif // !GOTHIC
556
557 #ifdef FALCON
558 ChessSquare FalconArray[2][BOARD_SIZE] = {
559     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
560         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
561     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
562         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
563 };
564 #else // !FALCON
565 #define FalconArray CapablancaArray
566 #endif // !FALCON
567
568 #else // !(BOARD_SIZE>=10)
569 #define XiangqiPosition FIDEArray
570 #define CapablancaArray FIDEArray
571 #define GothicArray FIDEArray
572 #define GreatArray FIDEArray
573 #endif // !(BOARD_SIZE>=10)
574
575 #if (BOARD_SIZE>=12)
576 ChessSquare CourierArray[2][BOARD_SIZE] = {
577     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
578         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
579     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
580         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
581 };
582 #else // !(BOARD_SIZE>=12)
583 #define CourierArray CapablancaArray
584 #endif // !(BOARD_SIZE>=12)
585
586
587 Board initialPosition;
588
589
590 /* Convert str to a rating. Checks for special cases of "----",
591
592    "++++", etc. Also strips ()'s */
593 int
594 string_to_rating(str)
595   char *str;
596 {
597   while(*str && !isdigit(*str)) ++str;
598   if (!*str)
599     return 0;   /* One of the special "no rating" cases */
600   else
601     return atoi(str);
602 }
603
604 void
605 ClearProgramStats()
606 {
607     /* Init programStats */
608     programStats.movelist[0] = 0;
609     programStats.depth = 0;
610     programStats.nr_moves = 0;
611     programStats.moves_left = 0;
612     programStats.nodes = 0;
613     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
614     programStats.score = 0;
615     programStats.got_only_move = 0;
616     programStats.got_fail = 0;
617     programStats.line_is_book = 0;
618 }
619
620 void
621 InitBackEnd1()
622 {
623     int matched, min, sec;
624
625     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
626
627     GetTimeMark(&programStartTime);
628     srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
629
630     ClearProgramStats();
631     programStats.ok_to_send = 1;
632     programStats.seen_stat = 0;
633
634     /*
635      * Initialize game list
636      */
637     ListNew(&gameList);
638
639
640     /*
641      * Internet chess server status
642      */
643     if (appData.icsActive) {
644         appData.matchMode = FALSE;
645         appData.matchGames = 0;
646 #if ZIPPY       
647         appData.noChessProgram = !appData.zippyPlay;
648 #else
649         appData.zippyPlay = FALSE;
650         appData.zippyTalk = FALSE;
651         appData.noChessProgram = TRUE;
652 #endif
653         if (*appData.icsHelper != NULLCHAR) {
654             appData.useTelnet = TRUE;
655             appData.telnetProgram = appData.icsHelper;
656         }
657     } else {
658         appData.zippyTalk = appData.zippyPlay = FALSE;
659     }
660
661     /* [AS] Initialize pv info list [HGM] and game state */
662     {
663         int i, j;
664
665         for( i=0; i<MAX_MOVES; i++ ) {
666             pvInfoList[i].depth = -1;
667             epStatus[i]=EP_NONE;
668             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
669         }
670     }
671
672     /*
673      * Parse timeControl resource
674      */
675     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
676                           appData.movesPerSession)) {
677         char buf[MSG_SIZ];
678         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
679         DisplayFatalError(buf, 0, 2);
680     }
681
682     /*
683      * Parse searchTime resource
684      */
685     if (*appData.searchTime != NULLCHAR) {
686         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
687         if (matched == 1) {
688             searchTime = min * 60;
689         } else if (matched == 2) {
690             searchTime = min * 60 + sec;
691         } else {
692             char buf[MSG_SIZ];
693             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
694             DisplayFatalError(buf, 0, 2);
695         }
696     }
697
698     /* [AS] Adjudication threshold */
699     adjudicateLossThreshold = appData.adjudicateLossThreshold;
700     
701     first.which = "first";
702     second.which = "second";
703     first.maybeThinking = second.maybeThinking = FALSE;
704     first.pr = second.pr = NoProc;
705     first.isr = second.isr = NULL;
706     first.sendTime = second.sendTime = 2;
707     first.sendDrawOffers = 1;
708     if (appData.firstPlaysBlack) {
709         first.twoMachinesColor = "black\n";
710         second.twoMachinesColor = "white\n";
711     } else {
712         first.twoMachinesColor = "white\n";
713         second.twoMachinesColor = "black\n";
714     }
715     first.program = appData.firstChessProgram;
716     second.program = appData.secondChessProgram;
717     first.host = appData.firstHost;
718     second.host = appData.secondHost;
719     first.dir = appData.firstDirectory;
720     second.dir = appData.secondDirectory;
721     first.other = &second;
722     second.other = &first;
723     first.initString = appData.initString;
724     second.initString = appData.secondInitString;
725     first.computerString = appData.firstComputerString;
726     second.computerString = appData.secondComputerString;
727     first.useSigint = second.useSigint = TRUE;
728     first.useSigterm = second.useSigterm = TRUE;
729     first.reuse = appData.reuseFirst;
730     second.reuse = appData.reuseSecond;
731     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
732     second.nps = appData.secondNPS;
733     first.useSetboard = second.useSetboard = FALSE;
734     first.useSAN = second.useSAN = FALSE;
735     first.usePing = second.usePing = FALSE;
736     first.lastPing = second.lastPing = 0;
737     first.lastPong = second.lastPong = 0;
738     first.usePlayother = second.usePlayother = FALSE;
739     first.useColors = second.useColors = TRUE;
740     first.useUsermove = second.useUsermove = FALSE;
741     first.sendICS = second.sendICS = FALSE;
742     first.sendName = second.sendName = appData.icsActive;
743     first.sdKludge = second.sdKludge = FALSE;
744     first.stKludge = second.stKludge = FALSE;
745     TidyProgramName(first.program, first.host, first.tidy);
746     TidyProgramName(second.program, second.host, second.tidy);
747     first.matchWins = second.matchWins = 0;
748     strcpy(first.variants, appData.variant);
749     strcpy(second.variants, appData.variant);
750     first.analysisSupport = second.analysisSupport = 2; /* detect */
751     first.analyzing = second.analyzing = FALSE;
752     first.initDone = second.initDone = FALSE;
753
754     /* New features added by Tord: */
755     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
756     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
757     /* End of new features added by Tord. */
758     first.fenOverride  = appData.fenOverride1;
759     second.fenOverride = appData.fenOverride2;
760
761     /* [HGM] time odds: set factor for each machine */
762     first.timeOdds  = appData.firstTimeOdds;
763     second.timeOdds = appData.secondTimeOdds;
764     { int norm = 1;
765         if(appData.timeOddsMode) {
766             norm = first.timeOdds;
767             if(norm > second.timeOdds) norm = second.timeOdds;
768         }
769         first.timeOdds /= norm;
770         second.timeOdds /= norm;
771     }
772
773     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
774     first.accumulateTC = appData.firstAccumulateTC;
775     second.accumulateTC = appData.secondAccumulateTC;
776     first.maxNrOfSessions = second.maxNrOfSessions = 1;
777
778     /* [HGM] debug */
779     first.debug = second.debug = FALSE;
780     first.supportsNPS = second.supportsNPS = UNKNOWN;
781
782     /* [HGM] options */
783     first.optionSettings  = appData.firstOptions;
784     second.optionSettings = appData.secondOptions;
785
786     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
787     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
788     first.isUCI = appData.firstIsUCI; /* [AS] */
789     second.isUCI = appData.secondIsUCI; /* [AS] */
790     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
791     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
792
793     if (appData.firstProtocolVersion > PROTOVER ||
794         appData.firstProtocolVersion < 1) {
795       char buf[MSG_SIZ];
796       sprintf(buf, _("protocol version %d not supported"),
797               appData.firstProtocolVersion);
798       DisplayFatalError(buf, 0, 2);
799     } else {
800       first.protocolVersion = appData.firstProtocolVersion;
801     }
802
803     if (appData.secondProtocolVersion > PROTOVER ||
804         appData.secondProtocolVersion < 1) {
805       char buf[MSG_SIZ];
806       sprintf(buf, _("protocol version %d not supported"),
807               appData.secondProtocolVersion);
808       DisplayFatalError(buf, 0, 2);
809     } else {
810       second.protocolVersion = appData.secondProtocolVersion;
811     }
812
813     if (appData.icsActive) {
814         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
815     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
816         appData.clockMode = FALSE;
817         first.sendTime = second.sendTime = 0;
818     }
819     
820 #if ZIPPY
821     /* Override some settings from environment variables, for backward
822        compatibility.  Unfortunately it's not feasible to have the env
823        vars just set defaults, at least in xboard.  Ugh.
824     */
825     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
826       ZippyInit();
827     }
828 #endif
829     
830     if (appData.noChessProgram) {
831         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
832         sprintf(programVersion, "%s", PACKAGE_STRING);
833     } else {
834 #if 0
835         char *p, *q;
836         q = first.program;
837         while (*q != ' ' && *q != NULLCHAR) q++;
838         p = q;
839         while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] backslash added */
840         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING + (q - p));
841         sprintf(programVersion, "%s + ", PACKAGE_STRING);
842         strncat(programVersion, p, q - p);
843 #else
844         /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
845         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
846         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
847 #endif
848     }
849
850     if (!appData.icsActive) {
851       char buf[MSG_SIZ];
852       /* Check for variants that are supported only in ICS mode,
853          or not at all.  Some that are accepted here nevertheless
854          have bugs; see comments below.
855       */
856       VariantClass variant = StringToVariant(appData.variant);
857       switch (variant) {
858       case VariantBughouse:     /* need four players and two boards */
859       case VariantKriegspiel:   /* need to hide pieces and move details */
860       /* case VariantFischeRandom: (Fabien: moved below) */
861         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
862         DisplayFatalError(buf, 0, 2);
863         return;
864
865       case VariantUnknown:
866       case VariantLoadable:
867       case Variant29:
868       case Variant30:
869       case Variant31:
870       case Variant32:
871       case Variant33:
872       case Variant34:
873       case Variant35:
874       case Variant36:
875       default:
876         sprintf(buf, _("Unknown variant name %s"), appData.variant);
877         DisplayFatalError(buf, 0, 2);
878         return;
879
880       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
881       case VariantFairy:      /* [HGM] TestLegality definitely off! */
882       case VariantGothic:     /* [HGM] should work */
883       case VariantCapablanca: /* [HGM] should work */
884       case VariantCourier:    /* [HGM] initial forced moves not implemented */
885       case VariantShogi:      /* [HGM] drops not tested for legality */
886       case VariantKnightmate: /* [HGM] should work */
887       case VariantCylinder:   /* [HGM] untested */
888       case VariantFalcon:     /* [HGM] untested */
889       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
890                                  offboard interposition not understood */
891       case VariantNormal:     /* definitely works! */
892       case VariantWildCastle: /* pieces not automatically shuffled */
893       case VariantNoCastle:   /* pieces not automatically shuffled */
894       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
895       case VariantLosers:     /* should work except for win condition,
896                                  and doesn't know captures are mandatory */
897       case VariantSuicide:    /* should work except for win condition,
898                                  and doesn't know captures are mandatory */
899       case VariantGiveaway:   /* should work except for win condition,
900                                  and doesn't know captures are mandatory */
901       case VariantTwoKings:   /* should work */
902       case VariantAtomic:     /* should work except for win condition */
903       case Variant3Check:     /* should work except for win condition */
904       case VariantShatranj:   /* should work except for all win conditions */
905       case VariantBerolina:   /* might work if TestLegality is off */
906       case VariantCapaRandom: /* should work */
907       case VariantJanus:      /* should work */
908       case VariantSuper:      /* experimental */
909       case VariantGreat:      /* experimental, requires legality testing to be off */
910         break;
911       }
912     }
913
914     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
915     InitEngineUCI( installDir, &second );
916 }
917
918 int NextIntegerFromString( char ** str, long * value )
919 {
920     int result = -1;
921     char * s = *str;
922
923     while( *s == ' ' || *s == '\t' ) {
924         s++;
925     }
926
927     *value = 0;
928
929     if( *s >= '0' && *s <= '9' ) {
930         while( *s >= '0' && *s <= '9' ) {
931             *value = *value * 10 + (*s - '0');
932             s++;
933         }
934
935         result = 0;
936     }
937
938     *str = s;
939
940     return result;
941 }
942
943 int NextTimeControlFromString( char ** str, long * value )
944 {
945     long temp;
946     int result = NextIntegerFromString( str, &temp );
947
948     if( result == 0 ) {
949         *value = temp * 60; /* Minutes */
950         if( **str == ':' ) {
951             (*str)++;
952             result = NextIntegerFromString( str, &temp );
953             *value += temp; /* Seconds */
954         }
955     }
956
957     return result;
958 }
959
960 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
961 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
962     int result = -1; long temp, temp2;
963
964     if(**str != '+') return -1; // old params remain in force!
965     (*str)++;
966     if( NextTimeControlFromString( str, &temp ) ) return -1;
967
968     if(**str != '/') {
969         /* time only: incremental or sudden-death time control */
970         if(**str == '+') { /* increment follows; read it */
971             (*str)++;
972             if(result = NextIntegerFromString( str, &temp2)) return -1;
973             *inc = temp2 * 1000;
974         } else *inc = 0;
975         *moves = 0; *tc = temp * 1000; 
976         return 0;
977     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
978
979     (*str)++; /* classical time control */
980     result = NextTimeControlFromString( str, &temp2);
981     if(result == 0) {
982         *moves = temp/60;
983         *tc    = temp2 * 1000;
984         *inc   = 0;
985     }
986     return result;
987 }
988
989 int GetTimeQuota(int movenr)
990 {   /* [HGM] get time to add from the multi-session time-control string */
991     int moves=1; /* kludge to force reading of first session */
992     long time, increment;
993     char *s = fullTimeControlString;
994
995     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
996     do {
997         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
998         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
999         if(movenr == -1) return time;    /* last move before new session     */
1000         if(!moves) return increment;     /* current session is incremental   */
1001         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1002     } while(movenr >= -1);               /* try again for next session       */
1003
1004     return 0; // no new time quota on this move
1005 }
1006
1007 int
1008 ParseTimeControl(tc, ti, mps)
1009      char *tc;
1010      int ti;
1011      int mps;
1012 {
1013 #if 0
1014     int matched, min, sec;
1015
1016     matched = sscanf(tc, "%d:%d", &min, &sec);
1017     if (matched == 1) {
1018         timeControl = min * 60 * 1000;
1019     } else if (matched == 2) {
1020         timeControl = (min * 60 + sec) * 1000;
1021     } else {
1022         return FALSE;
1023     }
1024 #else
1025     long tc1;
1026     long tc2;
1027     char buf[MSG_SIZ];
1028
1029     if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1030     if(ti > 0) {
1031         if(mps)
1032              sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1033         else sprintf(buf, "+%s+%d", tc, ti);
1034     } else {
1035         if(mps)
1036              sprintf(buf, "+%d/%s", mps, tc);
1037         else sprintf(buf, "+%s", tc);
1038     }
1039     fullTimeControlString = StrSave(buf);
1040
1041     if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1042         return FALSE;
1043     }
1044
1045     if( *tc == '/' ) {
1046         /* Parse second time control */
1047         tc++;
1048
1049         if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1050             return FALSE;
1051         }
1052
1053         if( tc2 == 0 ) {
1054             return FALSE;
1055         }
1056
1057         timeControl_2 = tc2 * 1000;
1058     }
1059     else {
1060         timeControl_2 = 0;
1061     }
1062
1063     if( tc1 == 0 ) {
1064         return FALSE;
1065     }
1066
1067     timeControl = tc1 * 1000;
1068 #endif
1069
1070     if (ti >= 0) {
1071         timeIncrement = ti * 1000;  /* convert to ms */
1072         movesPerSession = 0;
1073     } else {
1074         timeIncrement = 0;
1075         movesPerSession = mps;
1076     }
1077     return TRUE;
1078 }
1079
1080 void
1081 InitBackEnd2()
1082 {
1083     if (appData.debugMode) {
1084         fprintf(debugFP, "%s\n", programVersion);
1085     }
1086
1087     if (appData.matchGames > 0) {
1088         appData.matchMode = TRUE;
1089     } else if (appData.matchMode) {
1090         appData.matchGames = 1;
1091     }
1092     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1093         appData.matchGames = appData.sameColorGames;
1094     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1095         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1096         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1097     }
1098     Reset(TRUE, FALSE);
1099     if (appData.noChessProgram || first.protocolVersion == 1) {
1100       InitBackEnd3();
1101     } else {
1102       /* kludge: allow timeout for initial "feature" commands */
1103       FreezeUI();
1104       DisplayMessage("", _("Starting chess program"));
1105       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1106     }
1107 }
1108
1109 void
1110 InitBackEnd3 P((void))
1111 {
1112     GameMode initialMode;
1113     char buf[MSG_SIZ];
1114     int err;
1115
1116     InitChessProgram(&first, startedFromSetupPosition);
1117
1118
1119     if (appData.icsActive) {
1120 #ifdef WIN32
1121         /* [DM] Make a console window if needed [HGM] merged ifs */
1122         ConsoleCreate(); 
1123 #endif
1124         err = establish();
1125         if (err != 0) {
1126             if (*appData.icsCommPort != NULLCHAR) {
1127                 sprintf(buf, _("Could not open comm port %s"),  
1128                         appData.icsCommPort);
1129             } else {
1130                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1131                         appData.icsHost, appData.icsPort);
1132             }
1133             DisplayFatalError(buf, err, 1);
1134             return;
1135         }
1136         SetICSMode();
1137         telnetISR =
1138           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1139         fromUserISR =
1140           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1141     } else if (appData.noChessProgram) {
1142         SetNCPMode();
1143     } else {
1144         SetGNUMode();
1145     }
1146
1147     if (*appData.cmailGameName != NULLCHAR) {
1148         SetCmailMode();
1149         OpenLoopback(&cmailPR);
1150         cmailISR =
1151           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1152     }
1153     
1154     ThawUI();
1155     DisplayMessage("", "");
1156     if (StrCaseCmp(appData.initialMode, "") == 0) {
1157       initialMode = BeginningOfGame;
1158     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1159       initialMode = TwoMachinesPlay;
1160     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1161       initialMode = AnalyzeFile; 
1162     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1163       initialMode = AnalyzeMode;
1164     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1165       initialMode = MachinePlaysWhite;
1166     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1167       initialMode = MachinePlaysBlack;
1168     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1169       initialMode = EditGame;
1170     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1171       initialMode = EditPosition;
1172     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1173       initialMode = Training;
1174     } else {
1175       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1176       DisplayFatalError(buf, 0, 2);
1177       return;
1178     }
1179
1180     if (appData.matchMode) {
1181         /* Set up machine vs. machine match */
1182         if (appData.noChessProgram) {
1183             DisplayFatalError(_("Can't have a match with no chess programs"),
1184                               0, 2);
1185             return;
1186         }
1187         matchMode = TRUE;
1188         matchGame = 1;
1189         if (*appData.loadGameFile != NULLCHAR) {
1190             int index = appData.loadGameIndex; // [HGM] autoinc
1191             if(index<0) lastIndex = index = 1;
1192             if (!LoadGameFromFile(appData.loadGameFile,
1193                                   index,
1194                                   appData.loadGameFile, FALSE)) {
1195                 DisplayFatalError(_("Bad game file"), 0, 1);
1196                 return;
1197             }
1198         } else if (*appData.loadPositionFile != NULLCHAR) {
1199             int index = appData.loadPositionIndex; // [HGM] autoinc
1200             if(index<0) lastIndex = index = 1;
1201             if (!LoadPositionFromFile(appData.loadPositionFile,
1202                                       index,
1203                                       appData.loadPositionFile)) {
1204                 DisplayFatalError(_("Bad position file"), 0, 1);
1205                 return;
1206             }
1207         }
1208         TwoMachinesEvent();
1209     } else if (*appData.cmailGameName != NULLCHAR) {
1210         /* Set up cmail mode */
1211         ReloadCmailMsgEvent(TRUE);
1212     } else {
1213         /* Set up other modes */
1214         if (initialMode == AnalyzeFile) {
1215           if (*appData.loadGameFile == NULLCHAR) {
1216             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1217             return;
1218           }
1219         }
1220         if (*appData.loadGameFile != NULLCHAR) {
1221             (void) LoadGameFromFile(appData.loadGameFile,
1222                                     appData.loadGameIndex,
1223                                     appData.loadGameFile, TRUE);
1224         } else if (*appData.loadPositionFile != NULLCHAR) {
1225             (void) LoadPositionFromFile(appData.loadPositionFile,
1226                                         appData.loadPositionIndex,
1227                                         appData.loadPositionFile);
1228             /* [HGM] try to make self-starting even after FEN load */
1229             /* to allow automatic setup of fairy variants with wtm */
1230             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1231                 gameMode = BeginningOfGame;
1232                 setboardSpoiledMachineBlack = 1;
1233             }
1234             /* [HGM] loadPos: make that every new game uses the setup */
1235             /* from file as long as we do not switch variant          */
1236             if(!blackPlaysFirst) { int i;
1237                 startedFromPositionFile = TRUE;
1238                 CopyBoard(filePosition, boards[0]);
1239                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1240             }
1241         }
1242         if (initialMode == AnalyzeMode) {
1243           if (appData.noChessProgram) {
1244             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1245             return;
1246           }
1247           if (appData.icsActive) {
1248             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1249             return;
1250           }
1251           AnalyzeModeEvent();
1252         } else if (initialMode == AnalyzeFile) {
1253           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1254           ShowThinkingEvent();
1255           AnalyzeFileEvent();
1256           AnalysisPeriodicEvent(1);
1257         } else if (initialMode == MachinePlaysWhite) {
1258           if (appData.noChessProgram) {
1259             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1260                               0, 2);
1261             return;
1262           }
1263           if (appData.icsActive) {
1264             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1265                               0, 2);
1266             return;
1267           }
1268           MachineWhiteEvent();
1269         } else if (initialMode == MachinePlaysBlack) {
1270           if (appData.noChessProgram) {
1271             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1272                               0, 2);
1273             return;
1274           }
1275           if (appData.icsActive) {
1276             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1277                               0, 2);
1278             return;
1279           }
1280           MachineBlackEvent();
1281         } else if (initialMode == TwoMachinesPlay) {
1282           if (appData.noChessProgram) {
1283             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1284                               0, 2);
1285             return;
1286           }
1287           if (appData.icsActive) {
1288             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1289                               0, 2);
1290             return;
1291           }
1292           TwoMachinesEvent();
1293         } else if (initialMode == EditGame) {
1294           EditGameEvent();
1295         } else if (initialMode == EditPosition) {
1296           EditPositionEvent();
1297         } else if (initialMode == Training) {
1298           if (*appData.loadGameFile == NULLCHAR) {
1299             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1300             return;
1301           }
1302           TrainingEvent();
1303         }
1304     }
1305 }
1306
1307 /*
1308  * Establish will establish a contact to a remote host.port.
1309  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1310  *  used to talk to the host.
1311  * Returns 0 if okay, error code if not.
1312  */
1313 int
1314 establish()
1315 {
1316     char buf[MSG_SIZ];
1317
1318     if (*appData.icsCommPort != NULLCHAR) {
1319         /* Talk to the host through a serial comm port */
1320         return OpenCommPort(appData.icsCommPort, &icsPR);
1321
1322     } else if (*appData.gateway != NULLCHAR) {
1323         if (*appData.remoteShell == NULLCHAR) {
1324             /* Use the rcmd protocol to run telnet program on a gateway host */
1325             snprintf(buf, sizeof(buf), "%s %s %s",
1326                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1327             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1328
1329         } else {
1330             /* Use the rsh program to run telnet program on a gateway host */
1331             if (*appData.remoteUser == NULLCHAR) {
1332                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1333                         appData.gateway, appData.telnetProgram,
1334                         appData.icsHost, appData.icsPort);
1335             } else {
1336                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1337                         appData.remoteShell, appData.gateway, 
1338                         appData.remoteUser, appData.telnetProgram,
1339                         appData.icsHost, appData.icsPort);
1340             }
1341             return StartChildProcess(buf, "", &icsPR);
1342
1343         }
1344     } else if (appData.useTelnet) {
1345         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1346
1347     } else {
1348         /* TCP socket interface differs somewhat between
1349            Unix and NT; handle details in the front end.
1350            */
1351         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1352     }
1353 }
1354
1355 void
1356 show_bytes(fp, buf, count)
1357      FILE *fp;
1358      char *buf;
1359      int count;
1360 {
1361     while (count--) {
1362         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1363             fprintf(fp, "\\%03o", *buf & 0xff);
1364         } else {
1365             putc(*buf, fp);
1366         }
1367         buf++;
1368     }
1369     fflush(fp);
1370 }
1371
1372 /* Returns an errno value */
1373 int
1374 OutputMaybeTelnet(pr, message, count, outError)
1375      ProcRef pr;
1376      char *message;
1377      int count;
1378      int *outError;
1379 {
1380     char buf[8192], *p, *q, *buflim;
1381     int left, newcount, outcount;
1382
1383     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1384         *appData.gateway != NULLCHAR) {
1385         if (appData.debugMode) {
1386             fprintf(debugFP, ">ICS: ");
1387             show_bytes(debugFP, message, count);
1388             fprintf(debugFP, "\n");
1389         }
1390         return OutputToProcess(pr, message, count, outError);
1391     }
1392
1393     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1394     p = message;
1395     q = buf;
1396     left = count;
1397     newcount = 0;
1398     while (left) {
1399         if (q >= buflim) {
1400             if (appData.debugMode) {
1401                 fprintf(debugFP, ">ICS: ");
1402                 show_bytes(debugFP, buf, newcount);
1403                 fprintf(debugFP, "\n");
1404             }
1405             outcount = OutputToProcess(pr, buf, newcount, outError);
1406             if (outcount < newcount) return -1; /* to be sure */
1407             q = buf;
1408             newcount = 0;
1409         }
1410         if (*p == '\n') {
1411             *q++ = '\r';
1412             newcount++;
1413         } else if (((unsigned char) *p) == TN_IAC) {
1414             *q++ = (char) TN_IAC;
1415             newcount ++;
1416         }
1417         *q++ = *p++;
1418         newcount++;
1419         left--;
1420     }
1421     if (appData.debugMode) {
1422         fprintf(debugFP, ">ICS: ");
1423         show_bytes(debugFP, buf, newcount);
1424         fprintf(debugFP, "\n");
1425     }
1426     outcount = OutputToProcess(pr, buf, newcount, outError);
1427     if (outcount < newcount) return -1; /* to be sure */
1428     return count;
1429 }
1430
1431 void
1432 read_from_player(isr, closure, message, count, error)
1433      InputSourceRef isr;
1434      VOIDSTAR closure;
1435      char *message;
1436      int count;
1437      int error;
1438 {
1439     int outError, outCount;
1440     static int gotEof = 0;
1441
1442     /* Pass data read from player on to ICS */
1443     if (count > 0) {
1444         gotEof = 0;
1445         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1446         if (outCount < count) {
1447             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1448         }
1449     } else if (count < 0) {
1450         RemoveInputSource(isr);
1451         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1452     } else if (gotEof++ > 0) {
1453         RemoveInputSource(isr);
1454         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1455     }
1456 }
1457
1458 void
1459 KeepAlive()
1460 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1461     SendToICS("date\n");
1462     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1463 }
1464
1465 void
1466 SendToICS(s)
1467      char *s;
1468 {
1469     int count, outCount, outError;
1470
1471     if (icsPR == NULL) return;
1472
1473     count = strlen(s);
1474     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1475     if (outCount < count) {
1476         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1477     }
1478 }
1479
1480 /* This is used for sending logon scripts to the ICS. Sending
1481    without a delay causes problems when using timestamp on ICC
1482    (at least on my machine). */
1483 void
1484 SendToICSDelayed(s,msdelay)
1485      char *s;
1486      long msdelay;
1487 {
1488     int count, outCount, outError;
1489
1490     if (icsPR == NULL) return;
1491
1492     count = strlen(s);
1493     if (appData.debugMode) {
1494         fprintf(debugFP, ">ICS: ");
1495         show_bytes(debugFP, s, count);
1496         fprintf(debugFP, "\n");
1497     }
1498     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1499                                       msdelay);
1500     if (outCount < count) {
1501         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1502     }
1503 }
1504
1505
1506 /* Remove all highlighting escape sequences in s
1507    Also deletes any suffix starting with '(' 
1508    */
1509 char *
1510 StripHighlightAndTitle(s)
1511      char *s;
1512 {
1513     static char retbuf[MSG_SIZ];
1514     char *p = retbuf;
1515
1516     while (*s != NULLCHAR) {
1517         while (*s == '\033') {
1518             while (*s != NULLCHAR && !isalpha(*s)) s++;
1519             if (*s != NULLCHAR) s++;
1520         }
1521         while (*s != NULLCHAR && *s != '\033') {
1522             if (*s == '(' || *s == '[') {
1523                 *p = NULLCHAR;
1524                 return retbuf;
1525             }
1526             *p++ = *s++;
1527         }
1528     }
1529     *p = NULLCHAR;
1530     return retbuf;
1531 }
1532
1533 /* Remove all highlighting escape sequences in s */
1534 char *
1535 StripHighlight(s)
1536      char *s;
1537 {
1538     static char retbuf[MSG_SIZ];
1539     char *p = retbuf;
1540
1541     while (*s != NULLCHAR) {
1542         while (*s == '\033') {
1543             while (*s != NULLCHAR && !isalpha(*s)) s++;
1544             if (*s != NULLCHAR) s++;
1545         }
1546         while (*s != NULLCHAR && *s != '\033') {
1547             *p++ = *s++;
1548         }
1549     }
1550     *p = NULLCHAR;
1551     return retbuf;
1552 }
1553
1554 char *variantNames[] = VARIANT_NAMES;
1555 char *
1556 VariantName(v)
1557      VariantClass v;
1558 {
1559     return variantNames[v];
1560 }
1561
1562
1563 /* Identify a variant from the strings the chess servers use or the
1564    PGN Variant tag names we use. */
1565 VariantClass
1566 StringToVariant(e)
1567      char *e;
1568 {
1569     char *p;
1570     int wnum = -1;
1571     VariantClass v = VariantNormal;
1572     int i, found = FALSE;
1573     char buf[MSG_SIZ];
1574
1575     if (!e) return v;
1576
1577     /* [HGM] skip over optional board-size prefixes */
1578     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1579         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1580         while( *e++ != '_');
1581     }
1582
1583     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1584       if (StrCaseStr(e, variantNames[i])) {
1585         v = (VariantClass) i;
1586         found = TRUE;
1587         break;
1588       }
1589     }
1590
1591     if (!found) {
1592       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1593           || StrCaseStr(e, "wild/fr") 
1594           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1595         v = VariantFischeRandom;
1596       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1597                  (i = 1, p = StrCaseStr(e, "w"))) {
1598         p += i;
1599         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1600         if (isdigit(*p)) {
1601           wnum = atoi(p);
1602         } else {
1603           wnum = -1;
1604         }
1605         switch (wnum) {
1606         case 0: /* FICS only, actually */
1607         case 1:
1608           /* Castling legal even if K starts on d-file */
1609           v = VariantWildCastle;
1610           break;
1611         case 2:
1612         case 3:
1613         case 4:
1614           /* Castling illegal even if K & R happen to start in
1615              normal positions. */
1616           v = VariantNoCastle;
1617           break;
1618         case 5:
1619         case 7:
1620         case 8:
1621         case 10:
1622         case 11:
1623         case 12:
1624         case 13:
1625         case 14:
1626         case 15:
1627         case 18:
1628         case 19:
1629           /* Castling legal iff K & R start in normal positions */
1630           v = VariantNormal;
1631           break;
1632         case 6:
1633         case 20:
1634         case 21:
1635           /* Special wilds for position setup; unclear what to do here */
1636           v = VariantLoadable;
1637           break;
1638         case 9:
1639           /* Bizarre ICC game */
1640           v = VariantTwoKings;
1641           break;
1642         case 16:
1643           v = VariantKriegspiel;
1644           break;
1645         case 17:
1646           v = VariantLosers;
1647           break;
1648         case 22:
1649           v = VariantFischeRandom;
1650           break;
1651         case 23:
1652           v = VariantCrazyhouse;
1653           break;
1654         case 24:
1655           v = VariantBughouse;
1656           break;
1657         case 25:
1658           v = Variant3Check;
1659           break;
1660         case 26:
1661           /* Not quite the same as FICS suicide! */
1662           v = VariantGiveaway;
1663           break;
1664         case 27:
1665           v = VariantAtomic;
1666           break;
1667         case 28:
1668           v = VariantShatranj;
1669           break;
1670
1671         /* Temporary names for future ICC types.  The name *will* change in 
1672            the next xboard/WinBoard release after ICC defines it. */
1673         case 29:
1674           v = Variant29;
1675           break;
1676         case 30:
1677           v = Variant30;
1678           break;
1679         case 31:
1680           v = Variant31;
1681           break;
1682         case 32:
1683           v = Variant32;
1684           break;
1685         case 33:
1686           v = Variant33;
1687           break;
1688         case 34:
1689           v = Variant34;
1690           break;
1691         case 35:
1692           v = Variant35;
1693           break;
1694         case 36:
1695           v = Variant36;
1696           break;
1697         case 37:
1698           v = VariantShogi;
1699           break;
1700         case 38:
1701           v = VariantXiangqi;
1702           break;
1703         case 39:
1704           v = VariantCourier;
1705           break;
1706         case 40:
1707           v = VariantGothic;
1708           break;
1709         case 41:
1710           v = VariantCapablanca;
1711           break;
1712         case 42:
1713           v = VariantKnightmate;
1714           break;
1715         case 43:
1716           v = VariantFairy;
1717           break;
1718         case 44:
1719           v = VariantCylinder;
1720           break;
1721         case 45:
1722           v = VariantFalcon;
1723           break;
1724         case 46:
1725           v = VariantCapaRandom;
1726           break;
1727         case 47:
1728           v = VariantBerolina;
1729           break;
1730         case 48:
1731           v = VariantJanus;
1732           break;
1733         case 49:
1734           v = VariantSuper;
1735           break;
1736         case 50:
1737           v = VariantGreat;
1738           break;
1739         case -1:
1740           /* Found "wild" or "w" in the string but no number;
1741              must assume it's normal chess. */
1742           v = VariantNormal;
1743           break;
1744         default:
1745           sprintf(buf, _("Unknown wild type %d"), wnum);
1746           DisplayError(buf, 0);
1747           v = VariantUnknown;
1748           break;
1749         }
1750       }
1751     }
1752     if (appData.debugMode) {
1753       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1754               e, wnum, VariantName(v));
1755     }
1756     return v;
1757 }
1758
1759 static int leftover_start = 0, leftover_len = 0;
1760 char star_match[STAR_MATCH_N][MSG_SIZ];
1761
1762 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1763    advance *index beyond it, and set leftover_start to the new value of
1764    *index; else return FALSE.  If pattern contains the character '*', it
1765    matches any sequence of characters not containing '\r', '\n', or the
1766    character following the '*' (if any), and the matched sequence(s) are
1767    copied into star_match.
1768    */
1769 int
1770 looking_at(buf, index, pattern)
1771      char *buf;
1772      int *index;
1773      char *pattern;
1774 {
1775     char *bufp = &buf[*index], *patternp = pattern;
1776     int star_count = 0;
1777     char *matchp = star_match[0];
1778     
1779     for (;;) {
1780         if (*patternp == NULLCHAR) {
1781             *index = leftover_start = bufp - buf;
1782             *matchp = NULLCHAR;
1783             return TRUE;
1784         }
1785         if (*bufp == NULLCHAR) return FALSE;
1786         if (*patternp == '*') {
1787             if (*bufp == *(patternp + 1)) {
1788                 *matchp = NULLCHAR;
1789                 matchp = star_match[++star_count];
1790                 patternp += 2;
1791                 bufp++;
1792                 continue;
1793             } else if (*bufp == '\n' || *bufp == '\r') {
1794                 patternp++;
1795                 if (*patternp == NULLCHAR)
1796                   continue;
1797                 else
1798                   return FALSE;
1799             } else {
1800                 *matchp++ = *bufp++;
1801                 continue;
1802             }
1803         }
1804         if (*patternp != *bufp) return FALSE;
1805         patternp++;
1806         bufp++;
1807     }
1808 }
1809
1810 void
1811 SendToPlayer(data, length)
1812      char *data;
1813      int length;
1814 {
1815     int error, outCount;
1816     outCount = OutputToProcess(NoProc, data, length, &error);
1817     if (outCount < length) {
1818         DisplayFatalError(_("Error writing to display"), error, 1);
1819     }
1820 }
1821
1822 void
1823 PackHolding(packed, holding)
1824      char packed[];
1825      char *holding;
1826 {
1827     char *p = holding;
1828     char *q = packed;
1829     int runlength = 0;
1830     int curr = 9999;
1831     do {
1832         if (*p == curr) {
1833             runlength++;
1834         } else {
1835             switch (runlength) {
1836               case 0:
1837                 break;
1838               case 1:
1839                 *q++ = curr;
1840                 break;
1841               case 2:
1842                 *q++ = curr;
1843                 *q++ = curr;
1844                 break;
1845               default:
1846                 sprintf(q, "%d", runlength);
1847                 while (*q) q++;
1848                 *q++ = curr;
1849                 break;
1850             }
1851             runlength = 1;
1852             curr = *p;
1853         }
1854     } while (*p++);
1855     *q = NULLCHAR;
1856 }
1857
1858 /* Telnet protocol requests from the front end */
1859 void
1860 TelnetRequest(ddww, option)
1861      unsigned char ddww, option;
1862 {
1863     unsigned char msg[3];
1864     int outCount, outError;
1865
1866     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1867
1868     if (appData.debugMode) {
1869         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1870         switch (ddww) {
1871           case TN_DO:
1872             ddwwStr = "DO";
1873             break;
1874           case TN_DONT:
1875             ddwwStr = "DONT";
1876             break;
1877           case TN_WILL:
1878             ddwwStr = "WILL";
1879             break;
1880           case TN_WONT:
1881             ddwwStr = "WONT";
1882             break;
1883           default:
1884             ddwwStr = buf1;
1885             sprintf(buf1, "%d", ddww);
1886             break;
1887         }
1888         switch (option) {
1889           case TN_ECHO:
1890             optionStr = "ECHO";
1891             break;
1892           default:
1893             optionStr = buf2;
1894             sprintf(buf2, "%d", option);
1895             break;
1896         }
1897         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1898     }
1899     msg[0] = TN_IAC;
1900     msg[1] = ddww;
1901     msg[2] = option;
1902     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1903     if (outCount < 3) {
1904         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1905     }
1906 }
1907
1908 void
1909 DoEcho()
1910 {
1911     if (!appData.icsActive) return;
1912     TelnetRequest(TN_DO, TN_ECHO);
1913 }
1914
1915 void
1916 DontEcho()
1917 {
1918     if (!appData.icsActive) return;
1919     TelnetRequest(TN_DONT, TN_ECHO);
1920 }
1921
1922 void
1923 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1924 {
1925     /* put the holdings sent to us by the server on the board holdings area */
1926     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1927     char p;
1928     ChessSquare piece;
1929
1930     if(gameInfo.holdingsWidth < 2)  return;
1931
1932     if( (int)lowestPiece >= BlackPawn ) {
1933         holdingsColumn = 0;
1934         countsColumn = 1;
1935         holdingsStartRow = BOARD_HEIGHT-1;
1936         direction = -1;
1937     } else {
1938         holdingsColumn = BOARD_WIDTH-1;
1939         countsColumn = BOARD_WIDTH-2;
1940         holdingsStartRow = 0;
1941         direction = 1;
1942     }
1943
1944     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1945         board[i][holdingsColumn] = EmptySquare;
1946         board[i][countsColumn]   = (ChessSquare) 0;
1947     }
1948     while( (p=*holdings++) != NULLCHAR ) {
1949         piece = CharToPiece( ToUpper(p) );
1950         if(piece == EmptySquare) continue;
1951         /*j = (int) piece - (int) WhitePawn;*/
1952         j = PieceToNumber(piece);
1953         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1954         if(j < 0) continue;               /* should not happen */
1955         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1956         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1957         board[holdingsStartRow+j*direction][countsColumn]++;
1958     }
1959
1960 }
1961
1962
1963 void
1964 VariantSwitch(Board board, VariantClass newVariant)
1965 {
1966    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1967    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1968 //   Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1969
1970    startedFromPositionFile = FALSE;
1971    if(gameInfo.variant == newVariant) return;
1972
1973    /* [HGM] This routine is called each time an assignment is made to
1974     * gameInfo.variant during a game, to make sure the board sizes
1975     * are set to match the new variant. If that means adding or deleting
1976     * holdings, we shift the playing board accordingly
1977     * This kludge is needed because in ICS observe mode, we get boards
1978     * of an ongoing game without knowing the variant, and learn about the
1979     * latter only later. This can be because of the move list we requested,
1980     * in which case the game history is refilled from the beginning anyway,
1981     * but also when receiving holdings of a crazyhouse game. In the latter
1982     * case we want to add those holdings to the already received position.
1983     */
1984
1985
1986   if (appData.debugMode) {
1987     fprintf(debugFP, "Switch board from %s to %s\n",
1988                VariantName(gameInfo.variant), VariantName(newVariant));
1989     setbuf(debugFP, NULL);
1990   }
1991     shuffleOpenings = 0;       /* [HGM] shuffle */
1992     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1993     switch(newVariant) {
1994             case VariantShogi:
1995               newWidth = 9;  newHeight = 9;
1996               gameInfo.holdingsSize = 7;
1997             case VariantBughouse:
1998             case VariantCrazyhouse:
1999               newHoldingsWidth = 2; break;
2000             default:
2001               newHoldingsWidth = gameInfo.holdingsSize = 0;
2002     }
2003
2004     if(newWidth  != gameInfo.boardWidth  ||
2005        newHeight != gameInfo.boardHeight ||
2006        newHoldingsWidth != gameInfo.holdingsWidth ) {
2007
2008         /* shift position to new playing area, if needed */
2009         if(newHoldingsWidth > gameInfo.holdingsWidth) {
2010            for(i=0; i<BOARD_HEIGHT; i++) 
2011                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2012                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2013                                                      board[i][j];
2014            for(i=0; i<newHeight; i++) {
2015                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2016                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2017            }
2018         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2019            for(i=0; i<BOARD_HEIGHT; i++)
2020                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2021                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2022                                                  board[i][j];
2023         }
2024
2025         gameInfo.boardWidth  = newWidth;
2026         gameInfo.boardHeight = newHeight;
2027         gameInfo.holdingsWidth = newHoldingsWidth;
2028         gameInfo.variant = newVariant;
2029         InitDrawingSizes(-2, 0);
2030
2031         /* [HGM] The following should definitely be solved in a better way */
2032 #if 0
2033         CopyBoard(board, tempBoard); /* save position in case it is board[0] */
2034         for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
2035         saveEP = epStatus[0];
2036 #endif
2037         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2038 #if 0
2039         epStatus[0] = saveEP;
2040         for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
2041         CopyBoard(tempBoard, board); /* restore position received from ICS   */
2042 #endif
2043     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2044
2045     forwardMostMove = oldForwardMostMove;
2046     backwardMostMove = oldBackwardMostMove;
2047     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2048 }
2049
2050 static int loggedOn = FALSE;
2051
2052 /*-- Game start info cache: --*/
2053 int gs_gamenum;
2054 char gs_kind[MSG_SIZ];
2055 static char player1Name[128] = "";
2056 static char player2Name[128] = "";
2057 static int player1Rating = -1;
2058 static int player2Rating = -1;
2059 /*----------------------------*/
2060
2061 ColorClass curColor = ColorNormal;
2062 int suppressKibitz = 0;
2063
2064 void
2065 read_from_ics(isr, closure, data, count, error)
2066      InputSourceRef isr;
2067      VOIDSTAR closure;
2068      char *data;
2069      int count;
2070      int error;
2071 {
2072 #define BUF_SIZE 8192
2073 #define STARTED_NONE 0
2074 #define STARTED_MOVES 1
2075 #define STARTED_BOARD 2
2076 #define STARTED_OBSERVE 3
2077 #define STARTED_HOLDINGS 4
2078 #define STARTED_CHATTER 5
2079 #define STARTED_COMMENT 6
2080 #define STARTED_MOVES_NOHIDE 7
2081     
2082     static int started = STARTED_NONE;
2083     static char parse[20000];
2084     static int parse_pos = 0;
2085     static char buf[BUF_SIZE + 1];
2086     static int firstTime = TRUE, intfSet = FALSE;
2087     static ColorClass prevColor = ColorNormal;
2088     static int savingComment = FALSE;
2089     char str[500];
2090     int i, oldi;
2091     int buf_len;
2092     int next_out;
2093     int tkind;
2094     int backup;    /* [DM] For zippy color lines */
2095     char *p;
2096     char talker[MSG_SIZ]; // [HGM] chat
2097     int channel;
2098
2099     if (appData.debugMode) {
2100       if (!error) {
2101         fprintf(debugFP, "<ICS: ");
2102         show_bytes(debugFP, data, count);
2103         fprintf(debugFP, "\n");
2104       }
2105     }
2106
2107     if (appData.debugMode) { int f = forwardMostMove;
2108         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2109                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2110     }
2111     if (count > 0) {
2112         /* If last read ended with a partial line that we couldn't parse,
2113            prepend it to the new read and try again. */
2114         if (leftover_len > 0) {
2115             for (i=0; i<leftover_len; i++)
2116               buf[i] = buf[leftover_start + i];
2117         }
2118
2119         /* Copy in new characters, removing nulls and \r's */
2120         buf_len = leftover_len;
2121         for (i = 0; i < count; i++) {
2122             if (data[i] != NULLCHAR && data[i] != '\r')
2123               buf[buf_len++] = data[i];
2124             if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && 
2125                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') {
2126                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2127                 buf[buf_len++] = ' '; // replace by space (assumes ICS does not break lines within word)
2128             }
2129         }
2130
2131         buf[buf_len] = NULLCHAR;
2132         next_out = leftover_len;
2133         leftover_start = 0;
2134         
2135         i = 0;
2136         while (i < buf_len) {
2137             /* Deal with part of the TELNET option negotiation
2138                protocol.  We refuse to do anything beyond the
2139                defaults, except that we allow the WILL ECHO option,
2140                which ICS uses to turn off password echoing when we are
2141                directly connected to it.  We reject this option
2142                if localLineEditing mode is on (always on in xboard)
2143                and we are talking to port 23, which might be a real
2144                telnet server that will try to keep WILL ECHO on permanently.
2145              */
2146             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2147                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2148                 unsigned char option;
2149                 oldi = i;
2150                 switch ((unsigned char) buf[++i]) {
2151                   case TN_WILL:
2152                     if (appData.debugMode)
2153                       fprintf(debugFP, "\n<WILL ");
2154                     switch (option = (unsigned char) buf[++i]) {
2155                       case TN_ECHO:
2156                         if (appData.debugMode)
2157                           fprintf(debugFP, "ECHO ");
2158                         /* Reply only if this is a change, according
2159                            to the protocol rules. */
2160                         if (remoteEchoOption) break;
2161                         if (appData.localLineEditing &&
2162                             atoi(appData.icsPort) == TN_PORT) {
2163                             TelnetRequest(TN_DONT, TN_ECHO);
2164                         } else {
2165                             EchoOff();
2166                             TelnetRequest(TN_DO, TN_ECHO);
2167                             remoteEchoOption = TRUE;
2168                         }
2169                         break;
2170                       default:
2171                         if (appData.debugMode)
2172                           fprintf(debugFP, "%d ", option);
2173                         /* Whatever this is, we don't want it. */
2174                         TelnetRequest(TN_DONT, option);
2175                         break;
2176                     }
2177                     break;
2178                   case TN_WONT:
2179                     if (appData.debugMode)
2180                       fprintf(debugFP, "\n<WONT ");
2181                     switch (option = (unsigned char) buf[++i]) {
2182                       case TN_ECHO:
2183                         if (appData.debugMode)
2184                           fprintf(debugFP, "ECHO ");
2185                         /* Reply only if this is a change, according
2186                            to the protocol rules. */
2187                         if (!remoteEchoOption) break;
2188                         EchoOn();
2189                         TelnetRequest(TN_DONT, TN_ECHO);
2190                         remoteEchoOption = FALSE;
2191                         break;
2192                       default:
2193                         if (appData.debugMode)
2194                           fprintf(debugFP, "%d ", (unsigned char) option);
2195                         /* Whatever this is, it must already be turned
2196                            off, because we never agree to turn on
2197                            anything non-default, so according to the
2198                            protocol rules, we don't reply. */
2199                         break;
2200                     }
2201                     break;
2202                   case TN_DO:
2203                     if (appData.debugMode)
2204                       fprintf(debugFP, "\n<DO ");
2205                     switch (option = (unsigned char) buf[++i]) {
2206                       default:
2207                         /* Whatever this is, we refuse to do it. */
2208                         if (appData.debugMode)
2209                           fprintf(debugFP, "%d ", option);
2210                         TelnetRequest(TN_WONT, option);
2211                         break;
2212                     }
2213                     break;
2214                   case TN_DONT:
2215                     if (appData.debugMode)
2216                       fprintf(debugFP, "\n<DONT ");
2217                     switch (option = (unsigned char) buf[++i]) {
2218                       default:
2219                         if (appData.debugMode)
2220                           fprintf(debugFP, "%d ", option);
2221                         /* Whatever this is, we are already not doing
2222                            it, because we never agree to do anything
2223                            non-default, so according to the protocol
2224                            rules, we don't reply. */
2225                         break;
2226                     }
2227                     break;
2228                   case TN_IAC:
2229                     if (appData.debugMode)
2230                       fprintf(debugFP, "\n<IAC ");
2231                     /* Doubled IAC; pass it through */
2232                     i--;
2233                     break;
2234                   default:
2235                     if (appData.debugMode)
2236                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2237                     /* Drop all other telnet commands on the floor */
2238                     break;
2239                 }
2240                 if (oldi > next_out)
2241                   SendToPlayer(&buf[next_out], oldi - next_out);
2242                 if (++i > next_out)
2243                   next_out = i;
2244                 continue;
2245             }
2246                 
2247             /* OK, this at least will *usually* work */
2248             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2249                 loggedOn = TRUE;
2250             }
2251             
2252             if (loggedOn && !intfSet) {
2253                 if (ics_type == ICS_ICC) {
2254                   sprintf(str,
2255                           "/set-quietly interface %s\n/set-quietly style 12\n",
2256                           programVersion);
2257
2258                 } else if (ics_type == ICS_CHESSNET) {
2259                   sprintf(str, "/style 12\n");
2260                 } else {
2261                   strcpy(str, "alias $ @\n$set interface ");
2262                   strcat(str, programVersion);
2263                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2264 #ifdef WIN32
2265                   strcat(str, "$iset nohighlight 1\n");
2266 #endif
2267                   strcat(str, "$iset lock 1\n$style 12\n");
2268                 }
2269                 SendToICS(str);
2270                 intfSet = TRUE;
2271             }
2272
2273             if (started == STARTED_COMMENT) {
2274                 /* Accumulate characters in comment */
2275                 parse[parse_pos++] = buf[i];
2276                 if (buf[i] == '\n') {
2277                     parse[parse_pos] = NULLCHAR;
2278                     if(chattingPartner>=0) {
2279                         char mess[MSG_SIZ];
2280                         sprintf(mess, "%s%s", talker, parse);
2281                         OutputChatMessage(chattingPartner, mess);
2282                         chattingPartner = -1;
2283                     } else
2284                     if(!suppressKibitz) // [HGM] kibitz
2285                         AppendComment(forwardMostMove, StripHighlight(parse));
2286                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2287                         int nrDigit = 0, nrAlph = 0, i;
2288                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2289                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2290                         parse[parse_pos] = NULLCHAR;
2291                         // try to be smart: if it does not look like search info, it should go to
2292                         // ICS interaction window after all, not to engine-output window.
2293                         for(i=0; i<parse_pos; i++) { // count letters and digits
2294                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2295                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2296                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2297                         }
2298                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2299                             int depth=0; float score;
2300                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2301                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2302                                 pvInfoList[forwardMostMove-1].depth = depth;
2303                                 pvInfoList[forwardMostMove-1].score = 100*score;
2304                             }
2305                             OutputKibitz(suppressKibitz, parse);
2306                         } else {
2307                             char tmp[MSG_SIZ];
2308                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2309                             SendToPlayer(tmp, strlen(tmp));
2310                         }
2311                     }
2312                     started = STARTED_NONE;
2313                 } else {
2314                     /* Don't match patterns against characters in chatter */
2315                     i++;
2316                     continue;
2317                 }
2318             }
2319             if (started == STARTED_CHATTER) {
2320                 if (buf[i] != '\n') {
2321                     /* Don't match patterns against characters in chatter */
2322                     i++;
2323                     continue;
2324                 }
2325                 started = STARTED_NONE;
2326             }
2327
2328             /* Kludge to deal with rcmd protocol */
2329             if (firstTime && looking_at(buf, &i, "\001*")) {
2330                 DisplayFatalError(&buf[1], 0, 1);
2331                 continue;
2332             } else {
2333                 firstTime = FALSE;
2334             }
2335
2336             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2337                 ics_type = ICS_ICC;
2338                 ics_prefix = "/";
2339                 if (appData.debugMode)
2340                   fprintf(debugFP, "ics_type %d\n", ics_type);
2341                 continue;
2342             }
2343             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2344                 ics_type = ICS_FICS;
2345                 ics_prefix = "$";
2346                 if (appData.debugMode)
2347                   fprintf(debugFP, "ics_type %d\n", ics_type);
2348                 continue;
2349             }
2350             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2351                 ics_type = ICS_CHESSNET;
2352                 ics_prefix = "/";
2353                 if (appData.debugMode)
2354                   fprintf(debugFP, "ics_type %d\n", ics_type);
2355                 continue;
2356             }
2357
2358             if (!loggedOn &&
2359                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2360                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2361                  looking_at(buf, &i, "will be \"*\""))) {
2362               strcpy(ics_handle, star_match[0]);
2363               continue;
2364             }
2365
2366             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2367               char buf[MSG_SIZ];
2368               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2369               DisplayIcsInteractionTitle(buf);
2370               have_set_title = TRUE;
2371             }
2372
2373             /* skip finger notes */
2374             if (started == STARTED_NONE &&
2375                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2376                  (buf[i] == '1' && buf[i+1] == '0')) &&
2377                 buf[i+2] == ':' && buf[i+3] == ' ') {
2378               started = STARTED_CHATTER;
2379               i += 3;
2380               continue;
2381             }
2382
2383             /* skip formula vars */
2384             if (started == STARTED_NONE &&
2385                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2386               started = STARTED_CHATTER;
2387               i += 3;
2388               continue;
2389             }
2390
2391             oldi = i;
2392             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2393             if (appData.autoKibitz && started == STARTED_NONE && 
2394                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2395                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2396                 if(looking_at(buf, &i, "* kibitzes: ") &&
2397                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2398                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2399                         suppressKibitz = TRUE;
2400                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2401                                 && (gameMode == IcsPlayingWhite)) ||
2402                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2403                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2404                             started = STARTED_CHATTER; // own kibitz we simply discard
2405                         else {
2406                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2407                             parse_pos = 0; parse[0] = NULLCHAR;
2408                             savingComment = TRUE;
2409                             suppressKibitz = gameMode != IcsObserving ? 2 :
2410                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2411                         } 
2412                         continue;
2413                 } else
2414                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2415                     started = STARTED_CHATTER;
2416                     suppressKibitz = TRUE;
2417                 }
2418             } // [HGM] kibitz: end of patch
2419
2420 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2421
2422             // [HGM] chat: intercept tells by users for which we have an open chat window
2423             channel = -1;
2424             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2425                                            looking_at(buf, &i, "* whispers:") ||
2426                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2427                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2428                 int p;
2429                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2430                 chattingPartner = -1;
2431
2432                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2433                 for(p=0; p<MAX_CHAT; p++) {
2434                     if(channel == atoi(chatPartner[p])) {
2435                     talker[0] = '['; strcat(talker, "]");
2436                     chattingPartner = p; break;
2437                     }
2438                 } else
2439                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2440                 for(p=0; p<MAX_CHAT; p++) {
2441                     if(!strcmp("WHISPER", chatPartner[p])) {
2442                         talker[0] = '['; strcat(talker, "]");
2443                         chattingPartner = p; break;
2444                     }
2445                 }
2446                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2447                 for(p=0; p<MAX_CHAT; p++) if(!strcasecmp(talker+1, chatPartner[p])) {
2448                     talker[0] = 0;
2449                     chattingPartner = p; break;
2450                 }
2451                 if(chattingPartner<0) i = oldi; else {
2452                     started = STARTED_COMMENT;
2453                     parse_pos = 0; parse[0] = NULLCHAR;
2454                     savingComment = TRUE;
2455                     suppressKibitz = TRUE;
2456                 }
2457             } // [HGM] chat: end of patch
2458
2459             if (appData.zippyTalk || appData.zippyPlay) {
2460                 /* [DM] Backup address for color zippy lines */
2461                 backup = i;
2462 #if ZIPPY
2463        #ifdef WIN32
2464                if (loggedOn == TRUE)
2465                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2466                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2467        #else
2468                 if (ZippyControl(buf, &i) ||
2469                     ZippyConverse(buf, &i) ||
2470                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2471                       loggedOn = TRUE;
2472                       if (!appData.colorize) continue;
2473                 }
2474        #endif
2475 #endif
2476             } // [DM] 'else { ' deleted
2477                 if (
2478                     /* Regular tells and says */
2479                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2480                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2481                     looking_at(buf, &i, "* says: ") ||
2482                     /* Don't color "message" or "messages" output */
2483                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2484                     looking_at(buf, &i, "*. * at *:*: ") ||
2485                     looking_at(buf, &i, "--* (*:*): ") ||
2486                     /* Message notifications (same color as tells) */
2487                     looking_at(buf, &i, "* has left a message ") ||
2488                     looking_at(buf, &i, "* just sent you a message:\n") ||
2489                     /* Whispers and kibitzes */
2490                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2491                     looking_at(buf, &i, "* kibitzes: ") ||
2492                     /* Channel tells */
2493                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2494
2495                   if (tkind == 1 && strchr(star_match[0], ':')) {
2496                       /* Avoid "tells you:" spoofs in channels */
2497                      tkind = 3;
2498                   }
2499                   if (star_match[0][0] == NULLCHAR ||
2500                       strchr(star_match[0], ' ') ||
2501                       (tkind == 3 && strchr(star_match[1], ' '))) {
2502                     /* Reject bogus matches */
2503                     i = oldi;
2504                   } else {
2505                     if (appData.colorize) {
2506                       if (oldi > next_out) {
2507                         SendToPlayer(&buf[next_out], oldi - next_out);
2508                         next_out = oldi;
2509                       }
2510                       switch (tkind) {
2511                       case 1:
2512                         Colorize(ColorTell, FALSE);
2513                         curColor = ColorTell;
2514                         break;
2515                       case 2:
2516                         Colorize(ColorKibitz, FALSE);
2517                         curColor = ColorKibitz;
2518                         break;
2519                       case 3:
2520                         p = strrchr(star_match[1], '(');
2521                         if (p == NULL) {
2522                           p = star_match[1];
2523                         } else {
2524                           p++;
2525                         }
2526                         if (atoi(p) == 1) {
2527                           Colorize(ColorChannel1, FALSE);
2528                           curColor = ColorChannel1;
2529                         } else {
2530                           Colorize(ColorChannel, FALSE);
2531                           curColor = ColorChannel;
2532                         }
2533                         break;
2534                       case 5:
2535                         curColor = ColorNormal;
2536                         break;
2537                       }
2538                     }
2539                     if (started == STARTED_NONE && appData.autoComment &&
2540                         (gameMode == IcsObserving ||
2541                          gameMode == IcsPlayingWhite ||
2542                          gameMode == IcsPlayingBlack)) {
2543                       parse_pos = i - oldi;
2544                       memcpy(parse, &buf[oldi], parse_pos);
2545                       parse[parse_pos] = NULLCHAR;
2546                       started = STARTED_COMMENT;
2547                       savingComment = TRUE;
2548                     } else {
2549                       started = STARTED_CHATTER;
2550                       savingComment = FALSE;
2551                     }
2552                     loggedOn = TRUE;
2553                     continue;
2554                   }
2555                 }
2556
2557                 if (looking_at(buf, &i, "* s-shouts: ") ||
2558                     looking_at(buf, &i, "* c-shouts: ")) {
2559                     if (appData.colorize) {
2560                         if (oldi > next_out) {
2561                             SendToPlayer(&buf[next_out], oldi - next_out);
2562                             next_out = oldi;
2563                         }
2564                         Colorize(ColorSShout, FALSE);
2565                         curColor = ColorSShout;
2566                     }
2567                     loggedOn = TRUE;
2568                     started = STARTED_CHATTER;
2569                     continue;
2570                 }
2571
2572                 if (looking_at(buf, &i, "--->")) {
2573                     loggedOn = TRUE;
2574                     continue;
2575                 }
2576
2577                 if (looking_at(buf, &i, "* shouts: ") ||
2578                     looking_at(buf, &i, "--> ")) {
2579                     if (appData.colorize) {
2580                         if (oldi > next_out) {
2581                             SendToPlayer(&buf[next_out], oldi - next_out);
2582                             next_out = oldi;
2583                         }
2584                         Colorize(ColorShout, FALSE);
2585                         curColor = ColorShout;
2586                     }
2587                     loggedOn = TRUE;
2588                     started = STARTED_CHATTER;
2589                     continue;
2590                 }
2591
2592                 if (looking_at( buf, &i, "Challenge:")) {
2593                     if (appData.colorize) {
2594                         if (oldi > next_out) {
2595                             SendToPlayer(&buf[next_out], oldi - next_out);
2596                             next_out = oldi;
2597                         }
2598                         Colorize(ColorChallenge, FALSE);
2599                         curColor = ColorChallenge;
2600                     }
2601                     loggedOn = TRUE;
2602                     continue;
2603                 }
2604
2605                 if (looking_at(buf, &i, "* offers you") ||
2606                     looking_at(buf, &i, "* offers to be") ||
2607                     looking_at(buf, &i, "* would like to") ||
2608                     looking_at(buf, &i, "* requests to") ||
2609                     looking_at(buf, &i, "Your opponent offers") ||
2610                     looking_at(buf, &i, "Your opponent requests")) {
2611
2612                     if (appData.colorize) {
2613                         if (oldi > next_out) {
2614                             SendToPlayer(&buf[next_out], oldi - next_out);
2615                             next_out = oldi;
2616                         }
2617                         Colorize(ColorRequest, FALSE);
2618                         curColor = ColorRequest;
2619                     }
2620                     continue;
2621                 }
2622
2623                 if (looking_at(buf, &i, "* (*) seeking")) {
2624                     if (appData.colorize) {
2625                         if (oldi > next_out) {
2626                             SendToPlayer(&buf[next_out], oldi - next_out);
2627                             next_out = oldi;
2628                         }
2629                         Colorize(ColorSeek, FALSE);
2630                         curColor = ColorSeek;
2631                     }
2632                     continue;
2633             }
2634
2635             if (looking_at(buf, &i, "\\   ")) {
2636                 if (prevColor != ColorNormal) {
2637                     if (oldi > next_out) {
2638                         SendToPlayer(&buf[next_out], oldi - next_out);
2639                         next_out = oldi;
2640                     }
2641                     Colorize(prevColor, TRUE);
2642                     curColor = prevColor;
2643                 }
2644                 if (savingComment) {
2645                     parse_pos = i - oldi;
2646                     memcpy(parse, &buf[oldi], parse_pos);
2647                     parse[parse_pos] = NULLCHAR;
2648                     started = STARTED_COMMENT;
2649                 } else {
2650                     started = STARTED_CHATTER;
2651                 }
2652                 continue;
2653             }
2654
2655             if (looking_at(buf, &i, "Black Strength :") ||
2656                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2657                 looking_at(buf, &i, "<10>") ||
2658                 looking_at(buf, &i, "#@#")) {
2659                 /* Wrong board style */
2660                 loggedOn = TRUE;
2661                 SendToICS(ics_prefix);
2662                 SendToICS("set style 12\n");
2663                 SendToICS(ics_prefix);
2664                 SendToICS("refresh\n");
2665                 continue;
2666             }
2667             
2668             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2669                 ICSInitScript();
2670                 have_sent_ICS_logon = 1;
2671                 continue;
2672             }
2673               
2674             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2675                 (looking_at(buf, &i, "\n<12> ") ||
2676                  looking_at(buf, &i, "<12> "))) {
2677                 loggedOn = TRUE;
2678                 if (oldi > next_out) {
2679                     SendToPlayer(&buf[next_out], oldi - next_out);
2680                 }
2681                 next_out = i;
2682                 started = STARTED_BOARD;
2683                 parse_pos = 0;
2684                 continue;
2685             }
2686
2687             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2688                 looking_at(buf, &i, "<b1> ")) {
2689                 if (oldi > next_out) {
2690                     SendToPlayer(&buf[next_out], oldi - next_out);
2691                 }
2692                 next_out = i;
2693                 started = STARTED_HOLDINGS;
2694                 parse_pos = 0;
2695                 continue;
2696             }
2697
2698             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2699                 loggedOn = TRUE;
2700                 /* Header for a move list -- first line */
2701
2702                 switch (ics_getting_history) {
2703                   case H_FALSE:
2704                     switch (gameMode) {
2705                       case IcsIdle:
2706                       case BeginningOfGame:
2707                         /* User typed "moves" or "oldmoves" while we
2708                            were idle.  Pretend we asked for these
2709                            moves and soak them up so user can step
2710                            through them and/or save them.
2711                            */
2712                         Reset(FALSE, TRUE);
2713                         gameMode = IcsObserving;
2714                         ModeHighlight();
2715                         ics_gamenum = -1;
2716                         ics_getting_history = H_GOT_UNREQ_HEADER;
2717                         break;
2718                       case EditGame: /*?*/
2719                       case EditPosition: /*?*/
2720                         /* Should above feature work in these modes too? */
2721                         /* For now it doesn't */
2722                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2723                         break;
2724                       default:
2725                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2726                         break;
2727                     }
2728                     break;
2729                   case H_REQUESTED:
2730                     /* Is this the right one? */
2731                     if (gameInfo.white && gameInfo.black &&
2732                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2733                         strcmp(gameInfo.black, star_match[2]) == 0) {
2734                         /* All is well */
2735                         ics_getting_history = H_GOT_REQ_HEADER;
2736                     }
2737                     break;
2738                   case H_GOT_REQ_HEADER:
2739                   case H_GOT_UNREQ_HEADER:
2740                   case H_GOT_UNWANTED_HEADER:
2741                   case H_GETTING_MOVES:
2742                     /* Should not happen */
2743                     DisplayError(_("Error gathering move list: two headers"), 0);
2744                     ics_getting_history = H_FALSE;
2745                     break;
2746                 }
2747
2748                 /* Save player ratings into gameInfo if needed */
2749                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2750                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2751                     (gameInfo.whiteRating == -1 ||
2752                      gameInfo.blackRating == -1)) {
2753
2754                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2755                     gameInfo.blackRating = string_to_rating(star_match[3]);
2756                     if (appData.debugMode)
2757                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2758                               gameInfo.whiteRating, gameInfo.blackRating);
2759                 }
2760                 continue;
2761             }
2762
2763             if (looking_at(buf, &i,
2764               "* * match, initial time: * minute*, increment: * second")) {
2765                 /* Header for a move list -- second line */
2766                 /* Initial board will follow if this is a wild game */
2767                 if (gameInfo.event != NULL) free(gameInfo.event);
2768                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2769                 gameInfo.event = StrSave(str);
2770                 /* [HGM] we switched variant. Translate boards if needed. */
2771                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2772                 continue;
2773             }
2774
2775             if (looking_at(buf, &i, "Move  ")) {
2776                 /* Beginning of a move list */
2777                 switch (ics_getting_history) {
2778                   case H_FALSE:
2779                     /* Normally should not happen */
2780                     /* Maybe user hit reset while we were parsing */
2781                     break;
2782                   case H_REQUESTED:
2783                     /* Happens if we are ignoring a move list that is not
2784                      * the one we just requested.  Common if the user
2785                      * tries to observe two games without turning off
2786                      * getMoveList */
2787                     break;
2788                   case H_GETTING_MOVES:
2789                     /* Should not happen */
2790                     DisplayError(_("Error gathering move list: nested"), 0);
2791                     ics_getting_history = H_FALSE;
2792                     break;
2793                   case H_GOT_REQ_HEADER:
2794                     ics_getting_history = H_GETTING_MOVES;
2795                     started = STARTED_MOVES;
2796                     parse_pos = 0;
2797                     if (oldi > next_out) {
2798                         SendToPlayer(&buf[next_out], oldi - next_out);
2799                     }
2800                     break;
2801                   case H_GOT_UNREQ_HEADER:
2802                     ics_getting_history = H_GETTING_MOVES;
2803                     started = STARTED_MOVES_NOHIDE;
2804                     parse_pos = 0;
2805                     break;
2806                   case H_GOT_UNWANTED_HEADER:
2807                     ics_getting_history = H_FALSE;
2808                     break;
2809                 }
2810                 continue;
2811             }                           
2812             
2813             if (looking_at(buf, &i, "% ") ||
2814                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2815                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2816                 savingComment = FALSE;
2817                 switch (started) {
2818                   case STARTED_MOVES:
2819                   case STARTED_MOVES_NOHIDE:
2820                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2821                     parse[parse_pos + i - oldi] = NULLCHAR;
2822                     ParseGameHistory(parse);
2823 #if ZIPPY
2824                     if (appData.zippyPlay && first.initDone) {
2825                         FeedMovesToProgram(&first, forwardMostMove);
2826                         if (gameMode == IcsPlayingWhite) {
2827                             if (WhiteOnMove(forwardMostMove)) {
2828                                 if (first.sendTime) {
2829                                   if (first.useColors) {
2830                                     SendToProgram("black\n", &first); 
2831                                   }
2832                                   SendTimeRemaining(&first, TRUE);
2833                                 }
2834 #if 0
2835                                 if (first.useColors) {
2836                                   SendToProgram("white\ngo\n", &first);
2837                                 } else {
2838                                   SendToProgram("go\n", &first);
2839                                 }
2840 #else
2841                                 if (first.useColors) {
2842                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2843                                 }
2844                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2845 #endif
2846                                 first.maybeThinking = TRUE;
2847                             } else {
2848                                 if (first.usePlayother) {
2849                                   if (first.sendTime) {
2850                                     SendTimeRemaining(&first, TRUE);
2851                                   }
2852                                   SendToProgram("playother\n", &first);
2853                                   firstMove = FALSE;
2854                                 } else {
2855                                   firstMove = TRUE;
2856                                 }
2857                             }
2858                         } else if (gameMode == IcsPlayingBlack) {
2859                             if (!WhiteOnMove(forwardMostMove)) {
2860                                 if (first.sendTime) {
2861                                   if (first.useColors) {
2862                                     SendToProgram("white\n", &first);
2863                                   }
2864                                   SendTimeRemaining(&first, FALSE);
2865                                 }
2866 #if 0
2867                                 if (first.useColors) {
2868                                   SendToProgram("black\ngo\n", &first);
2869                                 } else {
2870                                   SendToProgram("go\n", &first);
2871                                 }
2872 #else
2873                                 if (first.useColors) {
2874                                   SendToProgram("black\n", &first);
2875                                 }
2876                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2877 #endif
2878                                 first.maybeThinking = TRUE;
2879                             } else {
2880                                 if (first.usePlayother) {
2881                                   if (first.sendTime) {
2882                                     SendTimeRemaining(&first, FALSE);
2883                                   }
2884                                   SendToProgram("playother\n", &first);
2885                                   firstMove = FALSE;
2886                                 } else {
2887                                   firstMove = TRUE;
2888                                 }
2889                             }
2890                         }                       
2891                     }
2892 #endif
2893                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2894                         /* Moves came from oldmoves or moves command
2895                            while we weren't doing anything else.
2896                            */
2897                         currentMove = forwardMostMove;
2898                         ClearHighlights();/*!!could figure this out*/
2899                         flipView = appData.flipView;
2900                         DrawPosition(FALSE, boards[currentMove]);
2901                         DisplayBothClocks();
2902                         sprintf(str, "%s vs. %s",
2903                                 gameInfo.white, gameInfo.black);
2904                         DisplayTitle(str);
2905                         gameMode = IcsIdle;
2906                     } else {
2907                         /* Moves were history of an active game */
2908                         if (gameInfo.resultDetails != NULL) {
2909                             free(gameInfo.resultDetails);
2910                             gameInfo.resultDetails = NULL;
2911                         }
2912                     }
2913                     HistorySet(parseList, backwardMostMove,
2914                                forwardMostMove, currentMove-1);
2915                     DisplayMove(currentMove - 1);
2916                     if (started == STARTED_MOVES) next_out = i;
2917                     started = STARTED_NONE;
2918                     ics_getting_history = H_FALSE;
2919                     break;
2920
2921                   case STARTED_OBSERVE:
2922                     started = STARTED_NONE;
2923                     SendToICS(ics_prefix);
2924                     SendToICS("refresh\n");
2925                     break;
2926
2927                   default:
2928                     break;
2929                 }
2930                 if(bookHit) { // [HGM] book: simulate book reply
2931                     static char bookMove[MSG_SIZ]; // a bit generous?
2932
2933                     programStats.nodes = programStats.depth = programStats.time = 
2934                     programStats.score = programStats.got_only_move = 0;
2935                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2936
2937                     strcpy(bookMove, "move ");
2938                     strcat(bookMove, bookHit);
2939                     HandleMachineMove(bookMove, &first);
2940                 }
2941                 continue;
2942             }
2943             
2944             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2945                  started == STARTED_HOLDINGS ||
2946                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2947                 /* Accumulate characters in move list or board */
2948                 parse[parse_pos++] = buf[i];
2949             }
2950             
2951             /* Start of game messages.  Mostly we detect start of game
2952                when the first board image arrives.  On some versions
2953                of the ICS, though, we need to do a "refresh" after starting
2954                to observe in order to get the current board right away. */
2955             if (looking_at(buf, &i, "Adding game * to observation list")) {
2956                 started = STARTED_OBSERVE;
2957                 continue;
2958             }
2959
2960             /* Handle auto-observe */
2961             if (appData.autoObserve &&
2962                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2963                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2964                 char *player;
2965                 /* Choose the player that was highlighted, if any. */
2966                 if (star_match[0][0] == '\033' ||
2967                     star_match[1][0] != '\033') {
2968                     player = star_match[0];
2969                 } else {
2970                     player = star_match[2];
2971                 }
2972                 sprintf(str, "%sobserve %s\n",
2973                         ics_prefix, StripHighlightAndTitle(player));
2974                 SendToICS(str);
2975
2976                 /* Save ratings from notify string */
2977                 strcpy(player1Name, star_match[0]);
2978                 player1Rating = string_to_rating(star_match[1]);
2979                 strcpy(player2Name, star_match[2]);
2980                 player2Rating = string_to_rating(star_match[3]);
2981
2982                 if (appData.debugMode)
2983                   fprintf(debugFP, 
2984                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2985                           player1Name, player1Rating,
2986                           player2Name, player2Rating);
2987
2988                 continue;
2989             }
2990
2991             /* Deal with automatic examine mode after a game,
2992                and with IcsObserving -> IcsExamining transition */
2993             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2994                 looking_at(buf, &i, "has made you an examiner of game *")) {
2995
2996                 int gamenum = atoi(star_match[0]);
2997                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2998                     gamenum == ics_gamenum) {
2999                     /* We were already playing or observing this game;
3000                        no need to refetch history */
3001                     gameMode = IcsExamining;
3002                     if (pausing) {
3003                         pauseExamForwardMostMove = forwardMostMove;
3004                     } else if (currentMove < forwardMostMove) {
3005                         ForwardInner(forwardMostMove);
3006                     }
3007                 } else {
3008                     /* I don't think this case really can happen */
3009                     SendToICS(ics_prefix);
3010                     SendToICS("refresh\n");
3011                 }
3012                 continue;
3013             }    
3014             
3015             /* Error messages */
3016 //          if (ics_user_moved) {
3017             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3018                 if (looking_at(buf, &i, "Illegal move") ||
3019                     looking_at(buf, &i, "Not a legal move") ||
3020                     looking_at(buf, &i, "Your king is in check") ||
3021                     looking_at(buf, &i, "It isn't your turn") ||
3022                     looking_at(buf, &i, "It is not your move")) {
3023                     /* Illegal move */
3024                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3025                         currentMove = --forwardMostMove;
3026                         DisplayMove(currentMove - 1); /* before DMError */
3027                         DrawPosition(FALSE, boards[currentMove]);
3028                         SwitchClocks();
3029                         DisplayBothClocks();
3030                     }
3031                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3032                     ics_user_moved = 0;
3033                     continue;
3034                 }
3035             }
3036
3037             if (looking_at(buf, &i, "still have time") ||
3038                 looking_at(buf, &i, "not out of time") ||
3039                 looking_at(buf, &i, "either player is out of time") ||
3040                 looking_at(buf, &i, "has timeseal; checking")) {
3041                 /* We must have called his flag a little too soon */
3042                 whiteFlag = blackFlag = FALSE;
3043                 continue;
3044             }
3045
3046             if (looking_at(buf, &i, "added * seconds to") ||
3047                 looking_at(buf, &i, "seconds were added to")) {
3048                 /* Update the clocks */
3049                 SendToICS(ics_prefix);
3050                 SendToICS("refresh\n");
3051                 continue;
3052             }
3053
3054             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3055                 ics_clock_paused = TRUE;
3056                 StopClocks();
3057                 continue;
3058             }
3059
3060             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3061                 ics_clock_paused = FALSE;
3062                 StartClocks();
3063                 continue;
3064             }
3065
3066             /* Grab player ratings from the Creating: message.
3067                Note we have to check for the special case when
3068                the ICS inserts things like [white] or [black]. */
3069             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3070                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3071                 /* star_matches:
3072                    0    player 1 name (not necessarily white)
3073                    1    player 1 rating
3074                    2    empty, white, or black (IGNORED)
3075                    3    player 2 name (not necessarily black)
3076                    4    player 2 rating
3077                    
3078                    The names/ratings are sorted out when the game
3079                    actually starts (below).
3080                 */
3081                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3082                 player1Rating = string_to_rating(star_match[1]);
3083                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3084                 player2Rating = string_to_rating(star_match[4]);
3085
3086                 if (appData.debugMode)
3087                   fprintf(debugFP, 
3088                           "Ratings from 'Creating:' %s %d, %s %d\n",
3089                           player1Name, player1Rating,
3090                           player2Name, player2Rating);
3091
3092                 continue;
3093             }
3094             
3095             /* Improved generic start/end-of-game messages */
3096             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3097                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3098                 /* If tkind == 0: */
3099                 /* star_match[0] is the game number */
3100                 /*           [1] is the white player's name */
3101                 /*           [2] is the black player's name */
3102                 /* For end-of-game: */
3103                 /*           [3] is the reason for the game end */
3104                 /*           [4] is a PGN end game-token, preceded by " " */
3105                 /* For start-of-game: */
3106                 /*           [3] begins with "Creating" or "Continuing" */
3107                 /*           [4] is " *" or empty (don't care). */
3108                 int gamenum = atoi(star_match[0]);
3109                 char *whitename, *blackname, *why, *endtoken;
3110                 ChessMove endtype = (ChessMove) 0;
3111
3112                 if (tkind == 0) {
3113                   whitename = star_match[1];
3114                   blackname = star_match[2];
3115                   why = star_match[3];
3116                   endtoken = star_match[4];
3117                 } else {
3118                   whitename = star_match[1];
3119                   blackname = star_match[3];
3120                   why = star_match[5];
3121                   endtoken = star_match[6];
3122                 }
3123
3124                 /* Game start messages */
3125                 if (strncmp(why, "Creating ", 9) == 0 ||
3126                     strncmp(why, "Continuing ", 11) == 0) {
3127                     gs_gamenum = gamenum;
3128                     strcpy(gs_kind, strchr(why, ' ') + 1);
3129 #if ZIPPY
3130                     if (appData.zippyPlay) {
3131                         ZippyGameStart(whitename, blackname);
3132                     }
3133 #endif /*ZIPPY*/
3134                     continue;
3135                 }
3136
3137                 /* Game end messages */
3138                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3139                     ics_gamenum != gamenum) {
3140                     continue;
3141                 }
3142                 while (endtoken[0] == ' ') endtoken++;
3143                 switch (endtoken[0]) {
3144                   case '*':
3145                   default:
3146                     endtype = GameUnfinished;
3147                     break;
3148                   case '0':
3149                     endtype = BlackWins;
3150                     break;
3151                   case '1':
3152                     if (endtoken[1] == '/')
3153                       endtype = GameIsDrawn;
3154                     else
3155                       endtype = WhiteWins;
3156                     break;
3157                 }
3158                 GameEnds(endtype, why, GE_ICS);
3159 #if ZIPPY
3160                 if (appData.zippyPlay && first.initDone) {
3161                     ZippyGameEnd(endtype, why);
3162                     if (first.pr == NULL) {
3163                       /* Start the next process early so that we'll
3164                          be ready for the next challenge */
3165                       StartChessProgram(&first);
3166                     }
3167                     /* Send "new" early, in case this command takes
3168                        a long time to finish, so that we'll be ready
3169                        for the next challenge. */
3170                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3171                     Reset(TRUE, TRUE);
3172                 }
3173 #endif /*ZIPPY*/
3174                 continue;
3175             }
3176
3177             if (looking_at(buf, &i, "Removing game * from observation") ||
3178                 looking_at(buf, &i, "no longer observing game *") ||
3179                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3180                 if (gameMode == IcsObserving &&
3181                     atoi(star_match[0]) == ics_gamenum)
3182                   {
3183                       /* icsEngineAnalyze */
3184                       if (appData.icsEngineAnalyze) {
3185                             ExitAnalyzeMode();
3186                             ModeHighlight();
3187                       }
3188                       StopClocks();
3189                       gameMode = IcsIdle;
3190                       ics_gamenum = -1;
3191                       ics_user_moved = FALSE;
3192                   }
3193                 continue;
3194             }
3195
3196             if (looking_at(buf, &i, "no longer examining game *")) {
3197                 if (gameMode == IcsExamining &&
3198                     atoi(star_match[0]) == ics_gamenum)
3199                   {
3200                       gameMode = IcsIdle;
3201                       ics_gamenum = -1;
3202                       ics_user_moved = FALSE;
3203                   }
3204                 continue;
3205             }
3206
3207             /* Advance leftover_start past any newlines we find,
3208                so only partial lines can get reparsed */
3209             if (looking_at(buf, &i, "\n")) {
3210                 prevColor = curColor;
3211                 if (curColor != ColorNormal) {
3212                     if (oldi > next_out) {
3213                         SendToPlayer(&buf[next_out], oldi - next_out);
3214                         next_out = oldi;
3215                     }
3216                     Colorize(ColorNormal, FALSE);
3217                     curColor = ColorNormal;
3218                 }
3219                 if (started == STARTED_BOARD) {
3220                     started = STARTED_NONE;
3221                     parse[parse_pos] = NULLCHAR;
3222                     ParseBoard12(parse);
3223                     ics_user_moved = 0;
3224
3225                     /* Send premove here */
3226                     if (appData.premove) {
3227                       char str[MSG_SIZ];
3228                       if (currentMove == 0 &&
3229                           gameMode == IcsPlayingWhite &&
3230                           appData.premoveWhite) {
3231                         sprintf(str, "%s%s\n", ics_prefix,
3232                                 appData.premoveWhiteText);
3233                         if (appData.debugMode)
3234                           fprintf(debugFP, "Sending premove:\n");
3235                         SendToICS(str);
3236                       } else if (currentMove == 1 &&
3237                                  gameMode == IcsPlayingBlack &&
3238                                  appData.premoveBlack) {
3239                         sprintf(str, "%s%s\n", ics_prefix,
3240                                 appData.premoveBlackText);
3241                         if (appData.debugMode)
3242                           fprintf(debugFP, "Sending premove:\n");
3243                         SendToICS(str);
3244                       } else if (gotPremove) {
3245                         gotPremove = 0;
3246                         ClearPremoveHighlights();
3247                         if (appData.debugMode)
3248                           fprintf(debugFP, "Sending premove:\n");
3249                           UserMoveEvent(premoveFromX, premoveFromY, 
3250                                         premoveToX, premoveToY, 
3251                                         premovePromoChar);
3252                       }
3253                     }
3254
3255                     /* Usually suppress following prompt */
3256                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3257                         if (looking_at(buf, &i, "*% ")) {
3258                             savingComment = FALSE;
3259                         }
3260                     }
3261                     next_out = i;
3262                 } else if (started == STARTED_HOLDINGS) {
3263                     int gamenum;
3264                     char new_piece[MSG_SIZ];
3265                     started = STARTED_NONE;
3266                     parse[parse_pos] = NULLCHAR;
3267                     if (appData.debugMode)
3268                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3269                                                         parse, currentMove);
3270                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3271                         gamenum == ics_gamenum) {
3272                         if (gameInfo.variant == VariantNormal) {
3273                           /* [HGM] We seem to switch variant during a game!
3274                            * Presumably no holdings were displayed, so we have
3275                            * to move the position two files to the right to
3276                            * create room for them!
3277                            */
3278                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3279                           /* Get a move list just to see the header, which
3280                              will tell us whether this is really bug or zh */
3281                           if (ics_getting_history == H_FALSE) {
3282                             ics_getting_history = H_REQUESTED;
3283                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3284                             SendToICS(str);
3285                           }
3286                         }
3287                         new_piece[0] = NULLCHAR;
3288                         sscanf(parse, "game %d white [%s black [%s <- %s",
3289                                &gamenum, white_holding, black_holding,
3290                                new_piece);
3291                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3292                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3293                         /* [HGM] copy holdings to board holdings area */
3294                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3295                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3296 #if ZIPPY
3297                         if (appData.zippyPlay && first.initDone) {
3298                             ZippyHoldings(white_holding, black_holding,
3299                                           new_piece);
3300                         }
3301 #endif /*ZIPPY*/
3302                         if (tinyLayout || smallLayout) {
3303                             char wh[16], bh[16];
3304                             PackHolding(wh, white_holding);
3305                             PackHolding(bh, black_holding);
3306                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3307                                     gameInfo.white, gameInfo.black);
3308                         } else {
3309                             sprintf(str, "%s [%s] vs. %s [%s]",
3310                                     gameInfo.white, white_holding,
3311                                     gameInfo.black, black_holding);
3312                         }
3313
3314                         DrawPosition(FALSE, boards[currentMove]);
3315                         DisplayTitle(str);
3316                     }
3317                     /* Suppress following prompt */
3318                     if (looking_at(buf, &i, "*% ")) {
3319                         savingComment = FALSE;
3320                     }
3321                     next_out = i;
3322                 }
3323                 continue;
3324             }
3325
3326             i++;                /* skip unparsed character and loop back */
3327         }
3328         
3329         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3330             started != STARTED_HOLDINGS && i > next_out) {
3331             SendToPlayer(&buf[next_out], i - next_out);
3332             next_out = i;
3333         }
3334         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3335         
3336         leftover_len = buf_len - leftover_start;
3337         /* if buffer ends with something we couldn't parse,
3338            reparse it after appending the next read */
3339         
3340     } else if (count == 0) {
3341         RemoveInputSource(isr);
3342         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3343     } else {
3344         DisplayFatalError(_("Error reading from ICS"), error, 1);
3345     }
3346 }
3347
3348
3349 /* Board style 12 looks like this:
3350    
3351    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
3352    
3353  * The "<12> " is stripped before it gets to this routine.  The two
3354  * trailing 0's (flip state and clock ticking) are later addition, and
3355  * some chess servers may not have them, or may have only the first.
3356  * Additional trailing fields may be added in the future.  
3357  */
3358
3359 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
3360
3361 #define RELATION_OBSERVING_PLAYED    0
3362 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3363 #define RELATION_PLAYING_MYMOVE      1
3364 #define RELATION_PLAYING_NOTMYMOVE  -1
3365 #define RELATION_EXAMINING           2
3366 #define RELATION_ISOLATED_BOARD     -3
3367 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3368
3369 void
3370 ParseBoard12(string)
3371      char *string;
3372
3373     GameMode newGameMode;
3374     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3375     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3376     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3377     char to_play, board_chars[200];
3378     char move_str[500], str[500], elapsed_time[500];
3379     char black[32], white[32];
3380     Board board;
3381     int prevMove = currentMove;
3382     int ticking = 2;
3383     ChessMove moveType;
3384     int fromX, fromY, toX, toY;
3385     char promoChar;
3386     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3387     char *bookHit = NULL; // [HGM] book
3388
3389     fromX = fromY = toX = toY = -1;
3390     
3391     newGame = FALSE;
3392
3393     if (appData.debugMode)
3394       fprintf(debugFP, _("Parsing board: %s\n"), string);
3395
3396     move_str[0] = NULLCHAR;
3397     elapsed_time[0] = NULLCHAR;
3398     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3399         int  i = 0, j;
3400         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3401             if(string[i] == ' ') { ranks++; files = 0; }
3402             else files++;
3403             i++;
3404         }
3405         for(j = 0; j <i; j++) board_chars[j] = string[j];
3406         board_chars[i] = '\0';
3407         string += i + 1;
3408     }
3409     n = sscanf(string, PATTERN, &to_play, &double_push,
3410                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3411                &gamenum, white, black, &relation, &basetime, &increment,
3412                &white_stren, &black_stren, &white_time, &black_time,
3413                &moveNum, str, elapsed_time, move_str, &ics_flip,
3414                &ticking);
3415
3416     if (n < 21) {
3417         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3418         DisplayError(str, 0);
3419         return;
3420     }
3421
3422     /* Convert the move number to internal form */
3423     moveNum = (moveNum - 1) * 2;
3424     if (to_play == 'B') moveNum++;
3425     if (moveNum >= MAX_MOVES) {
3426       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3427                         0, 1);
3428       return;
3429     }
3430     
3431     switch (relation) {
3432       case RELATION_OBSERVING_PLAYED:
3433       case RELATION_OBSERVING_STATIC:
3434         if (gamenum == -1) {
3435             /* Old ICC buglet */
3436             relation = RELATION_OBSERVING_STATIC;
3437         }
3438         newGameMode = IcsObserving;
3439         break;
3440       case RELATION_PLAYING_MYMOVE:
3441       case RELATION_PLAYING_NOTMYMOVE:
3442         newGameMode =
3443           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3444             IcsPlayingWhite : IcsPlayingBlack;
3445         break;
3446       case RELATION_EXAMINING:
3447         newGameMode = IcsExamining;
3448         break;
3449       case RELATION_ISOLATED_BOARD:
3450       default:
3451         /* Just display this board.  If user was doing something else,
3452            we will forget about it until the next board comes. */ 
3453         newGameMode = IcsIdle;
3454         break;
3455       case RELATION_STARTING_POSITION:
3456         newGameMode = gameMode;
3457         break;
3458     }
3459     
3460     /* Modify behavior for initial board display on move listing
3461        of wild games.
3462        */
3463     switch (ics_getting_history) {
3464       case H_FALSE:
3465       case H_REQUESTED:
3466         break;
3467       case H_GOT_REQ_HEADER:
3468       case H_GOT_UNREQ_HEADER:
3469         /* This is the initial position of the current game */
3470         gamenum = ics_gamenum;
3471         moveNum = 0;            /* old ICS bug workaround */
3472         if (to_play == 'B') {
3473           startedFromSetupPosition = TRUE;
3474           blackPlaysFirst = TRUE;
3475           moveNum = 1;
3476           if (forwardMostMove == 0) forwardMostMove = 1;
3477           if (backwardMostMove == 0) backwardMostMove = 1;
3478           if (currentMove == 0) currentMove = 1;
3479         }
3480         newGameMode = gameMode;
3481         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3482         break;
3483       case H_GOT_UNWANTED_HEADER:
3484         /* This is an initial board that we don't want */
3485         return;
3486       case H_GETTING_MOVES:
3487         /* Should not happen */
3488         DisplayError(_("Error gathering move list: extra board"), 0);
3489         ics_getting_history = H_FALSE;
3490         return;
3491     }
3492     
3493     /* Take action if this is the first board of a new game, or of a
3494        different game than is currently being displayed.  */
3495     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3496         relation == RELATION_ISOLATED_BOARD) {
3497         
3498         /* Forget the old game and get the history (if any) of the new one */
3499         if (gameMode != BeginningOfGame) {
3500           Reset(FALSE, TRUE);
3501         }
3502         newGame = TRUE;
3503         if (appData.autoRaiseBoard) BoardToTop();
3504         prevMove = -3;
3505         if (gamenum == -1) {
3506             newGameMode = IcsIdle;
3507         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3508                    appData.getMoveList) {
3509             /* Need to get game history */
3510             ics_getting_history = H_REQUESTED;
3511             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3512             SendToICS(str);
3513         }
3514         
3515         /* Initially flip the board to have black on the bottom if playing
3516            black or if the ICS flip flag is set, but let the user change
3517            it with the Flip View button. */
3518         flipView = appData.autoFlipView ? 
3519           (newGameMode == IcsPlayingBlack) || ics_flip :
3520           appData.flipView;
3521         
3522         /* Done with values from previous mode; copy in new ones */
3523         gameMode = newGameMode;
3524         ModeHighlight();
3525         ics_gamenum = gamenum;
3526         if (gamenum == gs_gamenum) {
3527             int klen = strlen(gs_kind);
3528             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3529             sprintf(str, "ICS %s", gs_kind);
3530             gameInfo.event = StrSave(str);
3531         } else {
3532             gameInfo.event = StrSave("ICS game");
3533         }
3534         gameInfo.site = StrSave(appData.icsHost);
3535         gameInfo.date = PGNDate();
3536         gameInfo.round = StrSave("-");
3537         gameInfo.white = StrSave(white);
3538         gameInfo.black = StrSave(black);
3539         timeControl = basetime * 60 * 1000;
3540         timeControl_2 = 0;
3541         timeIncrement = increment * 1000;
3542         movesPerSession = 0;
3543         gameInfo.timeControl = TimeControlTagValue();
3544         VariantSwitch(board, StringToVariant(gameInfo.event) );
3545   if (appData.debugMode) {
3546     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3547     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3548     setbuf(debugFP, NULL);
3549   }
3550
3551         gameInfo.outOfBook = NULL;
3552         
3553         /* Do we have the ratings? */
3554         if (strcmp(player1Name, white) == 0 &&
3555             strcmp(player2Name, black) == 0) {
3556             if (appData.debugMode)
3557               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3558                       player1Rating, player2Rating);
3559             gameInfo.whiteRating = player1Rating;
3560             gameInfo.blackRating = player2Rating;
3561         } else if (strcmp(player2Name, white) == 0 &&
3562                    strcmp(player1Name, black) == 0) {
3563             if (appData.debugMode)
3564               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3565                       player2Rating, player1Rating);
3566             gameInfo.whiteRating = player2Rating;
3567             gameInfo.blackRating = player1Rating;
3568         }
3569         player1Name[0] = player2Name[0] = NULLCHAR;
3570
3571         /* Silence shouts if requested */
3572         if (appData.quietPlay &&
3573             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3574             SendToICS(ics_prefix);
3575             SendToICS("set shout 0\n");
3576         }
3577     }
3578     
3579     /* Deal with midgame name changes */
3580     if (!newGame) {
3581         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3582             if (gameInfo.white) free(gameInfo.white);
3583             gameInfo.white = StrSave(white);
3584         }
3585         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3586             if (gameInfo.black) free(gameInfo.black);
3587             gameInfo.black = StrSave(black);
3588         }
3589     }
3590     
3591     /* Throw away game result if anything actually changes in examine mode */
3592     if (gameMode == IcsExamining && !newGame) {
3593         gameInfo.result = GameUnfinished;
3594         if (gameInfo.resultDetails != NULL) {
3595             free(gameInfo.resultDetails);
3596             gameInfo.resultDetails = NULL;
3597         }
3598     }
3599     
3600     /* In pausing && IcsExamining mode, we ignore boards coming
3601        in if they are in a different variation than we are. */
3602     if (pauseExamInvalid) return;
3603     if (pausing && gameMode == IcsExamining) {
3604         if (moveNum <= pauseExamForwardMostMove) {
3605             pauseExamInvalid = TRUE;
3606             forwardMostMove = pauseExamForwardMostMove;
3607             return;
3608         }
3609     }
3610     
3611   if (appData.debugMode) {
3612     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3613   }
3614     /* Parse the board */
3615     for (k = 0; k < ranks; k++) {
3616       for (j = 0; j < files; j++)
3617         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3618       if(gameInfo.holdingsWidth > 1) {
3619            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3620            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3621       }
3622     }
3623     CopyBoard(boards[moveNum], board);
3624     if (moveNum == 0) {
3625         startedFromSetupPosition =
3626           !CompareBoards(board, initialPosition);
3627         if(startedFromSetupPosition)
3628             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3629     }
3630
3631     /* [HGM] Set castling rights. Take the outermost Rooks,
3632        to make it also work for FRC opening positions. Note that board12
3633        is really defective for later FRC positions, as it has no way to
3634        indicate which Rook can castle if they are on the same side of King.
3635        For the initial position we grant rights to the outermost Rooks,
3636        and remember thos rights, and we then copy them on positions
3637        later in an FRC game. This means WB might not recognize castlings with
3638        Rooks that have moved back to their original position as illegal,
3639        but in ICS mode that is not its job anyway.
3640     */
3641     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3642     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3643
3644         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3645             if(board[0][i] == WhiteRook) j = i;
3646         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3647         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3648             if(board[0][i] == WhiteRook) j = i;
3649         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3650         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3651             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3652         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3653         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3654             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3655         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3656
3657         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3658         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3659             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3660         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3661             if(board[BOARD_HEIGHT-1][k] == bKing)
3662                 initialRights[5] = castlingRights[moveNum][5] = k;
3663     } else { int r;
3664         r = castlingRights[moveNum][0] = initialRights[0];
3665         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3666         r = castlingRights[moveNum][1] = initialRights[1];
3667         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3668         r = castlingRights[moveNum][3] = initialRights[3];
3669         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3670         r = castlingRights[moveNum][4] = initialRights[4];
3671         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3672         /* wildcastle kludge: always assume King has rights */
3673         r = castlingRights[moveNum][2] = initialRights[2];
3674         r = castlingRights[moveNum][5] = initialRights[5];
3675     }
3676     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3677     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3678
3679     
3680     if (ics_getting_history == H_GOT_REQ_HEADER ||
3681         ics_getting_history == H_GOT_UNREQ_HEADER) {
3682         /* This was an initial position from a move list, not
3683            the current position */
3684         return;
3685     }
3686     
3687     /* Update currentMove and known move number limits */
3688     newMove = newGame || moveNum > forwardMostMove;
3689
3690     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3691     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3692         takeback = forwardMostMove - moveNum;
3693         for (i = 0; i < takeback; i++) {
3694              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3695              SendToProgram("undo\n", &first);
3696         }
3697     }
3698
3699     if (newGame) {
3700         forwardMostMove = backwardMostMove = currentMove = moveNum;
3701         if (gameMode == IcsExamining && moveNum == 0) {
3702           /* Workaround for ICS limitation: we are not told the wild
3703              type when starting to examine a game.  But if we ask for
3704              the move list, the move list header will tell us */
3705             ics_getting_history = H_REQUESTED;
3706             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3707             SendToICS(str);
3708         }
3709     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3710                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3711         forwardMostMove = moveNum;
3712         if (!pausing || currentMove > forwardMostMove)
3713           currentMove = forwardMostMove;
3714     } else {
3715         /* New part of history that is not contiguous with old part */ 
3716         if (pausing && gameMode == IcsExamining) {
3717             pauseExamInvalid = TRUE;
3718             forwardMostMove = pauseExamForwardMostMove;
3719             return;
3720         }
3721         forwardMostMove = backwardMostMove = currentMove = moveNum;
3722         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3723             ics_getting_history = H_REQUESTED;
3724             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3725             SendToICS(str);
3726         }
3727     }
3728     
3729     /* Update the clocks */
3730     if (strchr(elapsed_time, '.')) {
3731       /* Time is in ms */
3732       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3733       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3734     } else {
3735       /* Time is in seconds */
3736       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3737       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3738     }
3739       
3740
3741 #if ZIPPY
3742     if (appData.zippyPlay && newGame &&
3743         gameMode != IcsObserving && gameMode != IcsIdle &&
3744         gameMode != IcsExamining)
3745       ZippyFirstBoard(moveNum, basetime, increment);
3746 #endif
3747     
3748     /* Put the move on the move list, first converting
3749        to canonical algebraic form. */
3750     if (moveNum > 0) {
3751   if (appData.debugMode) {
3752     if (appData.debugMode) { int f = forwardMostMove;
3753         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3754                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3755     }
3756     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3757     fprintf(debugFP, "moveNum = %d\n", moveNum);
3758     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3759     setbuf(debugFP, NULL);
3760   }
3761         if (moveNum <= backwardMostMove) {
3762             /* We don't know what the board looked like before
3763                this move.  Punt. */
3764             strcpy(parseList[moveNum - 1], move_str);
3765             strcat(parseList[moveNum - 1], " ");
3766             strcat(parseList[moveNum - 1], elapsed_time);
3767             moveList[moveNum - 1][0] = NULLCHAR;
3768         } else if (strcmp(move_str, "none") == 0) {
3769             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3770             /* Again, we don't know what the board looked like;
3771                this is really the start of the game. */
3772             parseList[moveNum - 1][0] = NULLCHAR;
3773             moveList[moveNum - 1][0] = NULLCHAR;
3774             backwardMostMove = moveNum;
3775             startedFromSetupPosition = TRUE;
3776             fromX = fromY = toX = toY = -1;
3777         } else {
3778           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3779           //                 So we parse the long-algebraic move string in stead of the SAN move
3780           int valid; char buf[MSG_SIZ], *prom;
3781
3782           // str looks something like "Q/a1-a2"; kill the slash
3783           if(str[1] == '/') 
3784                 sprintf(buf, "%c%s", str[0], str+2);
3785           else  strcpy(buf, str); // might be castling
3786           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3787                 strcat(buf, prom); // long move lacks promo specification!
3788           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3789                 if(appData.debugMode) 
3790                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3791                 strcpy(move_str, buf);
3792           }
3793           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3794                                 &fromX, &fromY, &toX, &toY, &promoChar)
3795                || ParseOneMove(buf, moveNum - 1, &moveType,
3796                                 &fromX, &fromY, &toX, &toY, &promoChar);
3797           // end of long SAN patch
3798           if (valid) {
3799             (void) CoordsToAlgebraic(boards[moveNum - 1],
3800                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3801                                      fromY, fromX, toY, toX, promoChar,
3802                                      parseList[moveNum-1]);
3803             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3804                              castlingRights[moveNum]) ) {
3805               case MT_NONE:
3806               case MT_STALEMATE:
3807               default:
3808                 break;
3809               case MT_CHECK:
3810                 if(gameInfo.variant != VariantShogi)
3811                     strcat(parseList[moveNum - 1], "+");
3812                 break;
3813               case MT_CHECKMATE:
3814               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3815                 strcat(parseList[moveNum - 1], "#");
3816                 break;
3817             }
3818             strcat(parseList[moveNum - 1], " ");
3819             strcat(parseList[moveNum - 1], elapsed_time);
3820             /* currentMoveString is set as a side-effect of ParseOneMove */
3821             strcpy(moveList[moveNum - 1], currentMoveString);
3822             strcat(moveList[moveNum - 1], "\n");
3823           } else {
3824             /* Move from ICS was illegal!?  Punt. */
3825   if (appData.debugMode) {
3826     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3827     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3828   }
3829 #if 0
3830             if (appData.testLegality && appData.debugMode) {
3831                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3832                 DisplayError(str, 0);
3833             }
3834 #endif
3835             strcpy(parseList[moveNum - 1], move_str);
3836             strcat(parseList[moveNum - 1], " ");
3837             strcat(parseList[moveNum - 1], elapsed_time);
3838             moveList[moveNum - 1][0] = NULLCHAR;
3839             fromX = fromY = toX = toY = -1;
3840           }
3841         }
3842   if (appData.debugMode) {
3843     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3844     setbuf(debugFP, NULL);
3845   }
3846
3847 #if ZIPPY
3848         /* Send move to chess program (BEFORE animating it). */
3849         if (appData.zippyPlay && !newGame && newMove && 
3850            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3851
3852             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3853                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3854                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3855                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3856                             move_str);
3857                     DisplayError(str, 0);
3858                 } else {
3859                     if (first.sendTime) {
3860                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3861                     }
3862                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3863                     if (firstMove && !bookHit) {
3864                         firstMove = FALSE;
3865                         if (first.useColors) {
3866                           SendToProgram(gameMode == IcsPlayingWhite ?
3867                                         "white\ngo\n" :
3868                                         "black\ngo\n", &first);
3869                         } else {
3870                           SendToProgram("go\n", &first);
3871                         }
3872                         first.maybeThinking = TRUE;
3873                     }
3874                 }
3875             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3876               if (moveList[moveNum - 1][0] == NULLCHAR) {
3877                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3878                 DisplayError(str, 0);
3879               } else {
3880                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3881                 SendMoveToProgram(moveNum - 1, &first);
3882               }
3883             }
3884         }
3885 #endif
3886     }
3887
3888     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3889         /* If move comes from a remote source, animate it.  If it
3890            isn't remote, it will have already been animated. */
3891         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3892             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3893         }
3894         if (!pausing && appData.highlightLastMove) {
3895             SetHighlights(fromX, fromY, toX, toY);
3896         }
3897     }
3898     
3899     /* Start the clocks */
3900     whiteFlag = blackFlag = FALSE;
3901     appData.clockMode = !(basetime == 0 && increment == 0);
3902     if (ticking == 0) {
3903       ics_clock_paused = TRUE;
3904       StopClocks();
3905     } else if (ticking == 1) {
3906       ics_clock_paused = FALSE;
3907     }
3908     if (gameMode == IcsIdle ||
3909         relation == RELATION_OBSERVING_STATIC ||
3910         relation == RELATION_EXAMINING ||
3911         ics_clock_paused)
3912       DisplayBothClocks();
3913     else
3914       StartClocks();
3915     
3916     /* Display opponents and material strengths */
3917     if (gameInfo.variant != VariantBughouse &&
3918         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3919         if (tinyLayout || smallLayout) {
3920             if(gameInfo.variant == VariantNormal)
3921                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3922                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3923                     basetime, increment);
3924             else
3925                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3926                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3927                     basetime, increment, (int) gameInfo.variant);
3928         } else {
3929             if(gameInfo.variant == VariantNormal)
3930                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3931                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3932                     basetime, increment);
3933             else
3934                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3935                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3936                     basetime, increment, VariantName(gameInfo.variant));
3937         }
3938         DisplayTitle(str);
3939   if (appData.debugMode) {
3940     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3941   }
3942     }
3943
3944    
3945     /* Display the board */
3946     if (!pausing && !appData.noGUI) {
3947       
3948       if (appData.premove)
3949           if (!gotPremove || 
3950              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3951              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3952               ClearPremoveHighlights();
3953
3954       DrawPosition(FALSE, boards[currentMove]);
3955       DisplayMove(moveNum - 1);
3956       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3957             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3958               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3959         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3960       }
3961     }
3962
3963     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3964 #if ZIPPY
3965     if(bookHit) { // [HGM] book: simulate book reply
3966         static char bookMove[MSG_SIZ]; // a bit generous?
3967
3968         programStats.nodes = programStats.depth = programStats.time = 
3969         programStats.score = programStats.got_only_move = 0;
3970         sprintf(programStats.movelist, "%s (xbook)", bookHit);
3971
3972         strcpy(bookMove, "move ");
3973         strcat(bookMove, bookHit);
3974         HandleMachineMove(bookMove, &first);
3975     }
3976 #endif
3977 }
3978
3979 void
3980 GetMoveListEvent()
3981 {
3982     char buf[MSG_SIZ];
3983     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3984         ics_getting_history = H_REQUESTED;
3985         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3986         SendToICS(buf);
3987     }
3988 }
3989
3990 void
3991 AnalysisPeriodicEvent(force)
3992      int force;
3993 {
3994     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3995          && !force) || !appData.periodicUpdates)
3996       return;
3997
3998     /* Send . command to Crafty to collect stats */
3999     SendToProgram(".\n", &first);
4000
4001     /* Don't send another until we get a response (this makes
4002        us stop sending to old Crafty's which don't understand
4003        the "." command (sending illegal cmds resets node count & time,
4004        which looks bad)) */
4005     programStats.ok_to_send = 0;
4006 }
4007
4008 void
4009 SendMoveToProgram(moveNum, cps)
4010      int moveNum;
4011      ChessProgramState *cps;
4012 {
4013     char buf[MSG_SIZ];
4014
4015     if (cps->useUsermove) {
4016       SendToProgram("usermove ", cps);
4017     }
4018     if (cps->useSAN) {
4019       char *space;
4020       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4021         int len = space - parseList[moveNum];
4022         memcpy(buf, parseList[moveNum], len);
4023         buf[len++] = '\n';
4024         buf[len] = NULLCHAR;
4025       } else {
4026         sprintf(buf, "%s\n", parseList[moveNum]);
4027       }
4028       SendToProgram(buf, cps);
4029     } else {
4030       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4031         AlphaRank(moveList[moveNum], 4);
4032         SendToProgram(moveList[moveNum], cps);
4033         AlphaRank(moveList[moveNum], 4); // and back
4034       } else
4035       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4036        * the engine. It would be nice to have a better way to identify castle 
4037        * moves here. */
4038       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4039                                                                          && cps->useOOCastle) {
4040         int fromX = moveList[moveNum][0] - AAA; 
4041         int fromY = moveList[moveNum][1] - ONE;
4042         int toX = moveList[moveNum][2] - AAA; 
4043         int toY = moveList[moveNum][3] - ONE;
4044         if((boards[moveNum][fromY][fromX] == WhiteKing 
4045             && boards[moveNum][toY][toX] == WhiteRook)
4046            || (boards[moveNum][fromY][fromX] == BlackKing 
4047                && boards[moveNum][toY][toX] == BlackRook)) {
4048           if(toX > fromX) SendToProgram("O-O\n", cps);
4049           else SendToProgram("O-O-O\n", cps);
4050         }
4051         else SendToProgram(moveList[moveNum], cps);
4052       }
4053       else SendToProgram(moveList[moveNum], cps);
4054       /* End of additions by Tord */
4055     }
4056
4057     /* [HGM] setting up the opening has brought engine in force mode! */
4058     /*       Send 'go' if we are in a mode where machine should play. */
4059     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4060         (gameMode == TwoMachinesPlay   ||
4061 #ifdef ZIPPY
4062          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4063 #endif
4064          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4065         SendToProgram("go\n", cps);
4066   if (appData.debugMode) {
4067     fprintf(debugFP, "(extra)\n");
4068   }
4069     }
4070     setboardSpoiledMachineBlack = 0;
4071 }
4072
4073 void
4074 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4075      ChessMove moveType;
4076      int fromX, fromY, toX, toY;
4077 {
4078     char user_move[MSG_SIZ];
4079
4080     switch (moveType) {
4081       default:
4082         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4083                 (int)moveType, fromX, fromY, toX, toY);
4084         DisplayError(user_move + strlen("say "), 0);
4085         break;
4086       case WhiteKingSideCastle:
4087       case BlackKingSideCastle:
4088       case WhiteQueenSideCastleWild:
4089       case BlackQueenSideCastleWild:
4090       /* PUSH Fabien */
4091       case WhiteHSideCastleFR:
4092       case BlackHSideCastleFR:
4093       /* POP Fabien */
4094         sprintf(user_move, "o-o\n");
4095         break;
4096       case WhiteQueenSideCastle:
4097       case BlackQueenSideCastle:
4098       case WhiteKingSideCastleWild:
4099       case BlackKingSideCastleWild:
4100       /* PUSH Fabien */
4101       case WhiteASideCastleFR:
4102       case BlackASideCastleFR:
4103       /* POP Fabien */
4104         sprintf(user_move, "o-o-o\n");
4105         break;
4106       case WhitePromotionQueen:
4107       case BlackPromotionQueen:
4108       case WhitePromotionRook:
4109       case BlackPromotionRook:
4110       case WhitePromotionBishop:
4111       case BlackPromotionBishop:
4112       case WhitePromotionKnight:
4113       case BlackPromotionKnight:
4114       case WhitePromotionKing:
4115       case BlackPromotionKing:
4116       case WhitePromotionChancellor:
4117       case BlackPromotionChancellor:
4118       case WhitePromotionArchbishop:
4119       case BlackPromotionArchbishop:
4120         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4121             sprintf(user_move, "%c%c%c%c=%c\n",
4122                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4123                 PieceToChar(WhiteFerz));
4124         else if(gameInfo.variant == VariantGreat)
4125             sprintf(user_move, "%c%c%c%c=%c\n",
4126                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4127                 PieceToChar(WhiteMan));
4128         else
4129             sprintf(user_move, "%c%c%c%c=%c\n",
4130                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4131                 PieceToChar(PromoPiece(moveType)));
4132         break;
4133       case WhiteDrop:
4134       case BlackDrop:
4135         sprintf(user_move, "%c@%c%c\n",
4136                 ToUpper(PieceToChar((ChessSquare) fromX)),
4137                 AAA + toX, ONE + toY);
4138         break;
4139       case NormalMove:
4140       case WhiteCapturesEnPassant:
4141       case BlackCapturesEnPassant:
4142       case IllegalMove:  /* could be a variant we don't quite understand */
4143         sprintf(user_move, "%c%c%c%c\n",
4144                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4145         break;
4146     }
4147     SendToICS(user_move);
4148     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4149         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4150 }
4151
4152 void
4153 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4154      int rf, ff, rt, ft;
4155      char promoChar;
4156      char move[7];
4157 {
4158     if (rf == DROP_RANK) {
4159         sprintf(move, "%c@%c%c\n",
4160                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4161     } else {
4162         if (promoChar == 'x' || promoChar == NULLCHAR) {
4163             sprintf(move, "%c%c%c%c\n",
4164                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4165         } else {
4166             sprintf(move, "%c%c%c%c%c\n",
4167                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4168         }
4169     }
4170 }
4171
4172 void
4173 ProcessICSInitScript(f)
4174      FILE *f;
4175 {
4176     char buf[MSG_SIZ];
4177
4178     while (fgets(buf, MSG_SIZ, f)) {
4179         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4180     }
4181
4182     fclose(f);
4183 }
4184
4185
4186 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4187 void
4188 AlphaRank(char *move, int n)
4189 {
4190 //    char *p = move, c; int x, y;
4191
4192     if (appData.debugMode) {
4193         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4194     }
4195
4196     if(move[1]=='*' && 
4197        move[2]>='0' && move[2]<='9' &&
4198        move[3]>='a' && move[3]<='x'    ) {
4199         move[1] = '@';
4200         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4201         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4202     } else
4203     if(move[0]>='0' && move[0]<='9' &&
4204        move[1]>='a' && move[1]<='x' &&
4205        move[2]>='0' && move[2]<='9' &&
4206        move[3]>='a' && move[3]<='x'    ) {
4207         /* input move, Shogi -> normal */
4208         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4209         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4210         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4211         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4212     } else
4213     if(move[1]=='@' &&
4214        move[3]>='0' && move[3]<='9' &&
4215        move[2]>='a' && move[2]<='x'    ) {
4216         move[1] = '*';
4217         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4218         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4219     } else
4220     if(
4221        move[0]>='a' && move[0]<='x' &&
4222        move[3]>='0' && move[3]<='9' &&
4223        move[2]>='a' && move[2]<='x'    ) {
4224          /* output move, normal -> Shogi */
4225         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4226         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4227         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4228         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4229         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4230     }
4231     if (appData.debugMode) {
4232         fprintf(debugFP, "   out = '%s'\n", move);
4233     }
4234 }
4235
4236 /* Parser for moves from gnuchess, ICS, or user typein box */
4237 Boolean
4238 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4239      char *move;
4240      int moveNum;
4241      ChessMove *moveType;
4242      int *fromX, *fromY, *toX, *toY;
4243      char *promoChar;
4244 {       
4245     if (appData.debugMode) {
4246         fprintf(debugFP, "move to parse: %s\n", move);
4247     }
4248     *moveType = yylexstr(moveNum, move);
4249
4250     switch (*moveType) {
4251       case WhitePromotionChancellor:
4252       case BlackPromotionChancellor:
4253       case WhitePromotionArchbishop:
4254       case BlackPromotionArchbishop:
4255       case WhitePromotionQueen:
4256       case BlackPromotionQueen:
4257       case WhitePromotionRook:
4258       case BlackPromotionRook:
4259       case WhitePromotionBishop:
4260       case BlackPromotionBishop:
4261       case WhitePromotionKnight:
4262       case BlackPromotionKnight:
4263       case WhitePromotionKing:
4264       case BlackPromotionKing:
4265       case NormalMove:
4266       case WhiteCapturesEnPassant:
4267       case BlackCapturesEnPassant:
4268       case WhiteKingSideCastle:
4269       case WhiteQueenSideCastle:
4270       case BlackKingSideCastle:
4271       case BlackQueenSideCastle:
4272       case WhiteKingSideCastleWild:
4273       case WhiteQueenSideCastleWild:
4274       case BlackKingSideCastleWild:
4275       case BlackQueenSideCastleWild:
4276       /* Code added by Tord: */
4277       case WhiteHSideCastleFR:
4278       case WhiteASideCastleFR:
4279       case BlackHSideCastleFR:
4280       case BlackASideCastleFR:
4281       /* End of code added by Tord */
4282       case IllegalMove:         /* bug or odd chess variant */
4283         *fromX = currentMoveString[0] - AAA;
4284         *fromY = currentMoveString[1] - ONE;
4285         *toX = currentMoveString[2] - AAA;
4286         *toY = currentMoveString[3] - ONE;
4287         *promoChar = currentMoveString[4];
4288         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4289             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4290     if (appData.debugMode) {
4291         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4292     }
4293             *fromX = *fromY = *toX = *toY = 0;
4294             return FALSE;
4295         }
4296         if (appData.testLegality) {
4297           return (*moveType != IllegalMove);
4298         } else {
4299           return !(fromX == fromY && toX == toY);
4300         }
4301
4302       case WhiteDrop:
4303       case BlackDrop:
4304         *fromX = *moveType == WhiteDrop ?
4305           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4306           (int) CharToPiece(ToLower(currentMoveString[0]));
4307         *fromY = DROP_RANK;
4308         *toX = currentMoveString[2] - AAA;
4309         *toY = currentMoveString[3] - ONE;
4310         *promoChar = NULLCHAR;
4311         return TRUE;
4312
4313       case AmbiguousMove:
4314       case ImpossibleMove:
4315       case (ChessMove) 0:       /* end of file */
4316       case ElapsedTime:
4317       case Comment:
4318       case PGNTag:
4319       case NAG:
4320       case WhiteWins:
4321       case BlackWins:
4322       case GameIsDrawn:
4323       default:
4324     if (appData.debugMode) {
4325         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4326     }
4327         /* bug? */
4328         *fromX = *fromY = *toX = *toY = 0;
4329         *promoChar = NULLCHAR;
4330         return FALSE;
4331     }
4332 }
4333
4334 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4335 // All positions will have equal probability, but the current method will not provide a unique
4336 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4337 #define DARK 1
4338 #define LITE 2
4339 #define ANY 3
4340
4341 int squaresLeft[4];
4342 int piecesLeft[(int)BlackPawn];
4343 int seed, nrOfShuffles;
4344
4345 void GetPositionNumber()
4346 {       // sets global variable seed
4347         int i;
4348
4349         seed = appData.defaultFrcPosition;
4350         if(seed < 0) { // randomize based on time for negative FRC position numbers
4351                 for(i=0; i<50; i++) seed += random();
4352                 seed = random() ^ random() >> 8 ^ random() << 8;
4353                 if(seed<0) seed = -seed;
4354         }
4355 }
4356
4357 int put(Board board, int pieceType, int rank, int n, int shade)
4358 // put the piece on the (n-1)-th empty squares of the given shade
4359 {
4360         int i;
4361
4362         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4363                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4364                         board[rank][i] = (ChessSquare) pieceType;
4365                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4366                         squaresLeft[ANY]--;
4367                         piecesLeft[pieceType]--; 
4368                         return i;
4369                 }
4370         }
4371         return -1;
4372 }
4373
4374
4375 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4376 // calculate where the next piece goes, (any empty square), and put it there
4377 {
4378         int i;
4379
4380         i = seed % squaresLeft[shade];
4381         nrOfShuffles *= squaresLeft[shade];
4382         seed /= squaresLeft[shade];
4383         put(board, pieceType, rank, i, shade);
4384 }
4385
4386 void AddTwoPieces(Board board, int pieceType, int rank)
4387 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4388 {
4389         int i, n=squaresLeft[ANY], j=n-1, k;
4390
4391         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4392         i = seed % k;  // pick one
4393         nrOfShuffles *= k;
4394         seed /= k;
4395         while(i >= j) i -= j--;
4396         j = n - 1 - j; i += j;
4397         put(board, pieceType, rank, j, ANY);
4398         put(board, pieceType, rank, i, ANY);
4399 }
4400
4401 void SetUpShuffle(Board board, int number)
4402 {
4403         int i, p, first=1;
4404
4405         GetPositionNumber(); nrOfShuffles = 1;
4406
4407         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4408         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4409         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4410
4411         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4412
4413         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4414             p = (int) board[0][i];
4415             if(p < (int) BlackPawn) piecesLeft[p] ++;
4416             board[0][i] = EmptySquare;
4417         }
4418
4419         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4420             // shuffles restricted to allow normal castling put KRR first
4421             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4422                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4423             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4424                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4425             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4426                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4427             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4428                 put(board, WhiteRook, 0, 0, ANY);
4429             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4430         }
4431
4432         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4433             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4434             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4435                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4436                 while(piecesLeft[p] >= 2) {
4437                     AddOnePiece(board, p, 0, LITE);
4438                     AddOnePiece(board, p, 0, DARK);
4439                 }
4440                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4441             }
4442
4443         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4444             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4445             // but we leave King and Rooks for last, to possibly obey FRC restriction
4446             if(p == (int)WhiteRook) continue;
4447             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4448             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4449         }
4450
4451         // now everything is placed, except perhaps King (Unicorn) and Rooks
4452
4453         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4454             // Last King gets castling rights
4455             while(piecesLeft[(int)WhiteUnicorn]) {
4456                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4457                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4458             }
4459
4460             while(piecesLeft[(int)WhiteKing]) {
4461                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4462                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4463             }
4464
4465
4466         } else {
4467             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4468             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4469         }
4470
4471         // Only Rooks can be left; simply place them all
4472         while(piecesLeft[(int)WhiteRook]) {
4473                 i = put(board, WhiteRook, 0, 0, ANY);
4474                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4475                         if(first) {
4476                                 first=0;
4477                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4478                         }
4479                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4480                 }
4481         }
4482         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4483             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4484         }
4485
4486         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4487 }
4488
4489 int SetCharTable( char *table, const char * map )
4490 /* [HGM] moved here from winboard.c because of its general usefulness */
4491 /*       Basically a safe strcpy that uses the last character as King */
4492 {
4493     int result = FALSE; int NrPieces;
4494
4495     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4496                     && NrPieces >= 12 && !(NrPieces&1)) {
4497         int i; /* [HGM] Accept even length from 12 to 34 */
4498
4499         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4500         for( i=0; i<NrPieces/2-1; i++ ) {
4501             table[i] = map[i];
4502             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4503         }
4504         table[(int) WhiteKing]  = map[NrPieces/2-1];
4505         table[(int) BlackKing]  = map[NrPieces-1];
4506
4507         result = TRUE;
4508     }
4509
4510     return result;
4511 }
4512
4513 void Prelude(Board board)
4514 {       // [HGM] superchess: random selection of exo-pieces
4515         int i, j, k; ChessSquare p; 
4516         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4517
4518         GetPositionNumber(); // use FRC position number
4519
4520         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4521             SetCharTable(pieceToChar, appData.pieceToCharTable);
4522             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4523                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4524         }
4525
4526         j = seed%4;                 seed /= 4; 
4527         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4528         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4529         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4530         j = seed%3 + (seed%3 >= j); seed /= 3; 
4531         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4532         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4533         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4534         j = seed%3;                 seed /= 3; 
4535         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4536         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4537         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4538         j = seed%2 + (seed%2 >= j); seed /= 2; 
4539         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4540         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4541         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4542         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4543         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4544         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4545         put(board, exoPieces[0],    0, 0, ANY);
4546         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4547 }
4548
4549 void
4550 InitPosition(redraw)
4551      int redraw;
4552 {
4553     ChessSquare (* pieces)[BOARD_SIZE];
4554     int i, j, pawnRow, overrule,
4555     oldx = gameInfo.boardWidth,
4556     oldy = gameInfo.boardHeight,
4557     oldh = gameInfo.holdingsWidth,
4558     oldv = gameInfo.variant;
4559
4560     currentMove = forwardMostMove = backwardMostMove = 0;
4561     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4562
4563     /* [AS] Initialize pv info list [HGM] and game status */
4564     {
4565         for( i=0; i<MAX_MOVES; i++ ) {
4566             pvInfoList[i].depth = 0;
4567             epStatus[i]=EP_NONE;
4568             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4569         }
4570
4571         initialRulePlies = 0; /* 50-move counter start */
4572
4573         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4574         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4575     }
4576
4577     
4578     /* [HGM] logic here is completely changed. In stead of full positions */
4579     /* the initialized data only consist of the two backranks. The switch */
4580     /* selects which one we will use, which is than copied to the Board   */
4581     /* initialPosition, which for the rest is initialized by Pawns and    */
4582     /* empty squares. This initial position is then copied to boards[0],  */
4583     /* possibly after shuffling, so that it remains available.            */
4584
4585     gameInfo.holdingsWidth = 0; /* default board sizes */
4586     gameInfo.boardWidth    = 8;
4587     gameInfo.boardHeight   = 8;
4588     gameInfo.holdingsSize  = 0;
4589     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4590     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4591     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4592
4593     switch (gameInfo.variant) {
4594     case VariantFischeRandom:
4595       shuffleOpenings = TRUE;
4596     default:
4597       pieces = FIDEArray;
4598       break;
4599     case VariantShatranj:
4600       pieces = ShatranjArray;
4601       nrCastlingRights = 0;
4602       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4603       break;
4604     case VariantTwoKings:
4605       pieces = twoKingsArray;
4606       break;
4607     case VariantCapaRandom:
4608       shuffleOpenings = TRUE;
4609     case VariantCapablanca:
4610       pieces = CapablancaArray;
4611       gameInfo.boardWidth = 10;
4612       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4613       break;
4614     case VariantGothic:
4615       pieces = GothicArray;
4616       gameInfo.boardWidth = 10;
4617       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4618       break;
4619     case VariantJanus:
4620       pieces = JanusArray;
4621       gameInfo.boardWidth = 10;
4622       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4623       nrCastlingRights = 6;
4624         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4625         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4626         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4627         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4628         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4629         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4630       break;
4631     case VariantFalcon:
4632       pieces = FalconArray;
4633       gameInfo.boardWidth = 10;
4634       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4635       break;
4636     case VariantXiangqi:
4637       pieces = XiangqiArray;
4638       gameInfo.boardWidth  = 9;
4639       gameInfo.boardHeight = 10;
4640       nrCastlingRights = 0;
4641       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4642       break;
4643     case VariantShogi:
4644       pieces = ShogiArray;
4645       gameInfo.boardWidth  = 9;
4646       gameInfo.boardHeight = 9;
4647       gameInfo.holdingsSize = 7;
4648       nrCastlingRights = 0;
4649       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4650       break;
4651     case VariantCourier:
4652       pieces = CourierArray;
4653       gameInfo.boardWidth  = 12;
4654       nrCastlingRights = 0;
4655       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4656       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4657       break;
4658     case VariantKnightmate:
4659       pieces = KnightmateArray;
4660       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4661       break;
4662     case VariantFairy:
4663       pieces = fairyArray;
4664       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4665       break;
4666     case VariantGreat:
4667       pieces = GreatArray;
4668       gameInfo.boardWidth = 10;
4669       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4670       gameInfo.holdingsSize = 8;
4671       break;
4672     case VariantSuper:
4673       pieces = FIDEArray;
4674       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4675       gameInfo.holdingsSize = 8;
4676       startedFromSetupPosition = TRUE;
4677       break;
4678     case VariantCrazyhouse:
4679     case VariantBughouse:
4680       pieces = FIDEArray;
4681       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4682       gameInfo.holdingsSize = 5;
4683       break;
4684     case VariantWildCastle:
4685       pieces = FIDEArray;
4686       /* !!?shuffle with kings guaranteed to be on d or e file */
4687       shuffleOpenings = 1;
4688       break;
4689     case VariantNoCastle:
4690       pieces = FIDEArray;
4691       nrCastlingRights = 0;
4692       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4693       /* !!?unconstrained back-rank shuffle */
4694       shuffleOpenings = 1;
4695       break;
4696     }
4697
4698     overrule = 0;
4699     if(appData.NrFiles >= 0) {
4700         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4701         gameInfo.boardWidth = appData.NrFiles;
4702     }
4703     if(appData.NrRanks >= 0) {
4704         gameInfo.boardHeight = appData.NrRanks;
4705     }
4706     if(appData.holdingsSize >= 0) {
4707         i = appData.holdingsSize;
4708         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4709         gameInfo.holdingsSize = i;
4710     }
4711     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4712     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4713         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4714
4715     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4716     if(pawnRow < 1) pawnRow = 1;
4717
4718     /* User pieceToChar list overrules defaults */
4719     if(appData.pieceToCharTable != NULL)
4720         SetCharTable(pieceToChar, appData.pieceToCharTable);
4721
4722     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4723
4724         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4725             s = (ChessSquare) 0; /* account holding counts in guard band */
4726         for( i=0; i<BOARD_HEIGHT; i++ )
4727             initialPosition[i][j] = s;
4728
4729         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4730         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4731         initialPosition[pawnRow][j] = WhitePawn;
4732         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4733         if(gameInfo.variant == VariantXiangqi) {
4734             if(j&1) {
4735                 initialPosition[pawnRow][j] = 
4736                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4737                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4738                    initialPosition[2][j] = WhiteCannon;
4739                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4740                 }
4741             }
4742         }
4743         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4744     }
4745     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4746
4747             j=BOARD_LEFT+1;
4748             initialPosition[1][j] = WhiteBishop;
4749             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4750             j=BOARD_RGHT-2;
4751             initialPosition[1][j] = WhiteRook;
4752             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4753     }
4754
4755     if( nrCastlingRights == -1) {
4756         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4757         /*       This sets default castling rights from none to normal corners   */
4758         /* Variants with other castling rights must set them themselves above    */
4759         nrCastlingRights = 6;
4760        
4761         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4762         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4763         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4764         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4765         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4766         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4767      }
4768
4769      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4770      if(gameInfo.variant == VariantGreat) { // promotion commoners
4771         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4772         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4773         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4774         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4775      }
4776 #if 0
4777     if(gameInfo.variant == VariantFischeRandom) {
4778       if( appData.defaultFrcPosition < 0 ) {
4779         ShuffleFRC( initialPosition );
4780       }
4781       else {
4782         SetupFRC( initialPosition, appData.defaultFrcPosition );
4783       }
4784       startedFromSetupPosition = TRUE;
4785     } else 
4786 #else
4787   if (appData.debugMode) {
4788     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4789   }
4790     if(shuffleOpenings) {
4791         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4792         startedFromSetupPosition = TRUE;
4793     }
4794 #endif
4795     if(startedFromPositionFile) {
4796       /* [HGM] loadPos: use PositionFile for every new game */
4797       CopyBoard(initialPosition, filePosition);
4798       for(i=0; i<nrCastlingRights; i++)
4799           castlingRights[0][i] = initialRights[i] = fileRights[i];
4800       startedFromSetupPosition = TRUE;
4801     }
4802
4803     CopyBoard(boards[0], initialPosition);
4804
4805     if(oldx != gameInfo.boardWidth ||
4806        oldy != gameInfo.boardHeight ||
4807        oldh != gameInfo.holdingsWidth
4808 #ifdef GOTHIC
4809        || oldv == VariantGothic ||        // For licensing popups
4810        gameInfo.variant == VariantGothic
4811 #endif
4812 #ifdef FALCON
4813        || oldv == VariantFalcon ||
4814        gameInfo.variant == VariantFalcon
4815 #endif
4816                                          )
4817             InitDrawingSizes(-2 ,0);
4818
4819     if (redraw)
4820       DrawPosition(TRUE, boards[currentMove]);
4821 }
4822
4823 void
4824 SendBoard(cps, moveNum)
4825      ChessProgramState *cps;
4826      int moveNum;
4827 {
4828     char message[MSG_SIZ];
4829     
4830     if (cps->useSetboard) {
4831       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4832       sprintf(message, "setboard %s\n", fen);
4833       SendToProgram(message, cps);
4834       free(fen);
4835
4836     } else {
4837       ChessSquare *bp;
4838       int i, j;
4839       /* Kludge to set black to move, avoiding the troublesome and now
4840        * deprecated "black" command.
4841        */
4842       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4843
4844       SendToProgram("edit\n", cps);
4845       SendToProgram("#\n", cps);
4846       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4847         bp = &boards[moveNum][i][BOARD_LEFT];
4848         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4849           if ((int) *bp < (int) BlackPawn) {
4850             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4851                     AAA + j, ONE + i);
4852             if(message[0] == '+' || message[0] == '~') {
4853                 sprintf(message, "%c%c%c+\n",
4854                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4855                         AAA + j, ONE + i);
4856             }
4857             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4858                 message[1] = BOARD_RGHT   - 1 - j + '1';
4859                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4860             }
4861             SendToProgram(message, cps);
4862           }
4863         }
4864       }
4865     
4866       SendToProgram("c\n", cps);
4867       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4868         bp = &boards[moveNum][i][BOARD_LEFT];
4869         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4870           if (((int) *bp != (int) EmptySquare)
4871               && ((int) *bp >= (int) BlackPawn)) {
4872             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4873                     AAA + j, ONE + i);
4874             if(message[0] == '+' || message[0] == '~') {
4875                 sprintf(message, "%c%c%c+\n",
4876                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4877                         AAA + j, ONE + i);
4878             }
4879             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4880                 message[1] = BOARD_RGHT   - 1 - j + '1';
4881                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4882             }
4883             SendToProgram(message, cps);
4884           }
4885         }
4886       }
4887     
4888       SendToProgram(".\n", cps);
4889     }
4890     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4891 }
4892
4893 int
4894 IsPromotion(fromX, fromY, toX, toY)
4895      int fromX, fromY, toX, toY;
4896 {
4897     /* [HGM] add Shogi promotions */
4898     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4899     ChessSquare piece;
4900
4901     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4902       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4903    /* [HGM] Note to self: line above also weeds out drops */
4904     piece = boards[currentMove][fromY][fromX];
4905     if(gameInfo.variant == VariantShogi) {
4906         promotionZoneSize = 3;
4907         highestPromotingPiece = (int)WhiteKing;
4908         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4909            and if in normal chess we then allow promotion to King, why not
4910            allow promotion of other piece in Shogi?                         */
4911     }
4912     if((int)piece >= BlackPawn) {
4913         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4914              return FALSE;
4915         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4916     } else {
4917         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4918            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4919     }
4920     return ( (int)piece <= highestPromotingPiece );
4921 }
4922
4923 int
4924 InPalace(row, column)
4925      int row, column;
4926 {   /* [HGM] for Xiangqi */
4927     if( (row < 3 || row > BOARD_HEIGHT-4) &&
4928          column < (BOARD_WIDTH + 4)/2 &&
4929          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4930     return FALSE;
4931 }
4932
4933 int
4934 PieceForSquare (x, y)
4935      int x;
4936      int y;
4937 {
4938   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4939      return -1;
4940   else
4941      return boards[currentMove][y][x];
4942 }
4943
4944 int
4945 OKToStartUserMove(x, y)
4946      int x, y;
4947 {
4948     ChessSquare from_piece;
4949     int white_piece;
4950
4951     if (matchMode) return FALSE;
4952     if (gameMode == EditPosition) return TRUE;
4953
4954     if (x >= 0 && y >= 0)
4955       from_piece = boards[currentMove][y][x];
4956     else
4957       from_piece = EmptySquare;
4958
4959     if (from_piece == EmptySquare) return FALSE;
4960
4961     white_piece = (int)from_piece >= (int)WhitePawn &&
4962       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4963
4964     switch (gameMode) {
4965       case PlayFromGameFile:
4966       case AnalyzeFile:
4967       case TwoMachinesPlay:
4968       case EndOfGame:
4969         return FALSE;
4970
4971       case IcsObserving:
4972       case IcsIdle:
4973         return FALSE;
4974
4975       case MachinePlaysWhite:
4976       case IcsPlayingBlack:
4977         if (appData.zippyPlay) return FALSE;
4978         if (white_piece) {
4979             DisplayMoveError(_("You are playing Black"));
4980             return FALSE;
4981         }
4982         break;
4983
4984       case MachinePlaysBlack:
4985       case IcsPlayingWhite:
4986         if (appData.zippyPlay) return FALSE;
4987         if (!white_piece) {
4988             DisplayMoveError(_("You are playing White"));
4989             return FALSE;
4990         }
4991         break;
4992
4993       case EditGame:
4994         if (!white_piece && WhiteOnMove(currentMove)) {
4995             DisplayMoveError(_("It is White's turn"));
4996             return FALSE;
4997         }           
4998         if (white_piece && !WhiteOnMove(currentMove)) {
4999             DisplayMoveError(_("It is Black's turn"));
5000             return FALSE;
5001         }           
5002         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5003             /* Editing correspondence game history */
5004             /* Could disallow this or prompt for confirmation */
5005             cmailOldMove = -1;
5006         }
5007         if (currentMove < forwardMostMove) {
5008             /* Discarding moves */
5009             /* Could prompt for confirmation here,
5010                but I don't think that's such a good idea */
5011             forwardMostMove = currentMove;
5012         }
5013         break;
5014
5015       case BeginningOfGame:
5016         if (appData.icsActive) return FALSE;
5017         if (!appData.noChessProgram) {
5018             if (!white_piece) {
5019                 DisplayMoveError(_("You are playing White"));
5020                 return FALSE;
5021             }
5022         }
5023         break;
5024         
5025       case Training:
5026         if (!white_piece && WhiteOnMove(currentMove)) {
5027             DisplayMoveError(_("It is White's turn"));
5028             return FALSE;
5029         }           
5030         if (white_piece && !WhiteOnMove(currentMove)) {
5031             DisplayMoveError(_("It is Black's turn"));
5032             return FALSE;
5033         }           
5034         break;
5035
5036       default:
5037       case IcsExamining:
5038         break;
5039     }
5040     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5041         && gameMode != AnalyzeFile && gameMode != Training) {
5042         DisplayMoveError(_("Displayed position is not current"));
5043         return FALSE;
5044     }
5045     return TRUE;
5046 }
5047
5048 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5049 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5050 int lastLoadGameUseList = FALSE;
5051 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5052 ChessMove lastLoadGameStart = (ChessMove) 0;
5053
5054
5055 ChessMove
5056 UserMoveTest(fromX, fromY, toX, toY, promoChar)
5057      int fromX, fromY, toX, toY;
5058      int promoChar;
5059 {
5060     ChessMove moveType;
5061     ChessSquare pdown, pup;
5062
5063     if (fromX < 0 || fromY < 0) return ImpossibleMove;
5064     if ((fromX == toX) && (fromY == toY)) {
5065         return ImpossibleMove;
5066     }
5067
5068     /* [HGM] suppress all moves into holdings area and guard band */
5069     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5070             return ImpossibleMove;
5071
5072     /* [HGM] <sameColor> moved to here from winboard.c */
5073     /* note: this code seems to exist for filtering out some obviously illegal premoves */
5074     pdown = boards[currentMove][fromY][fromX];
5075     pup = boards[currentMove][toY][toX];
5076     if (    gameMode != EditPosition &&
5077             (WhitePawn <= pdown && pdown < BlackPawn &&
5078              WhitePawn <= pup && pup < BlackPawn  ||
5079              BlackPawn <= pdown && pdown < EmptySquare &&
5080              BlackPawn <= pup && pup < EmptySquare 
5081             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5082                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5083                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) 
5084         )           )
5085          return ImpossibleMove;
5086
5087     /* Check if the user is playing in turn.  This is complicated because we
5088        let the user "pick up" a piece before it is his turn.  So the piece he
5089        tried to pick up may have been captured by the time he puts it down!
5090        Therefore we use the color the user is supposed to be playing in this
5091        test, not the color of the piece that is currently on the starting
5092        square---except in EditGame mode, where the user is playing both
5093        sides; fortunately there the capture race can't happen.  (It can
5094        now happen in IcsExamining mode, but that's just too bad.  The user
5095        will get a somewhat confusing message in that case.)
5096        */
5097
5098     switch (gameMode) {
5099       case PlayFromGameFile:
5100       case AnalyzeFile:
5101       case TwoMachinesPlay:
5102       case EndOfGame:
5103       case IcsObserving:
5104       case IcsIdle:
5105         /* We switched into a game mode where moves are not accepted,
5106            perhaps while the mouse button was down. */
5107         return ImpossibleMove;
5108
5109       case MachinePlaysWhite:
5110         /* User is moving for Black */
5111         if (WhiteOnMove(currentMove)) {
5112             DisplayMoveError(_("It is White's turn"));
5113             return ImpossibleMove;
5114         }
5115         break;
5116
5117       case MachinePlaysBlack:
5118         /* User is moving for White */
5119         if (!WhiteOnMove(currentMove)) {
5120             DisplayMoveError(_("It is Black's turn"));
5121             return ImpossibleMove;
5122         }
5123         break;
5124
5125       case EditGame:
5126       case IcsExamining:
5127       case BeginningOfGame:
5128       case AnalyzeMode:
5129       case Training:
5130         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5131             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5132             /* User is moving for Black */
5133             if (WhiteOnMove(currentMove)) {
5134                 DisplayMoveError(_("It is White's turn"));
5135                 return ImpossibleMove;
5136             }
5137         } else {
5138             /* User is moving for White */
5139             if (!WhiteOnMove(currentMove)) {
5140                 DisplayMoveError(_("It is Black's turn"));
5141                 return ImpossibleMove;
5142             }
5143         }
5144         break;
5145
5146       case IcsPlayingBlack:
5147         /* User is moving for Black */
5148         if (WhiteOnMove(currentMove)) {
5149             if (!appData.premove) {
5150                 DisplayMoveError(_("It is White's turn"));
5151             } else if (toX >= 0 && toY >= 0) {
5152                 premoveToX = toX;
5153                 premoveToY = toY;
5154                 premoveFromX = fromX;
5155                 premoveFromY = fromY;
5156                 premovePromoChar = promoChar;
5157                 gotPremove = 1;
5158                 if (appData.debugMode) 
5159                     fprintf(debugFP, "Got premove: fromX %d,"
5160                             "fromY %d, toX %d, toY %d\n",
5161                             fromX, fromY, toX, toY);
5162             }
5163             return ImpossibleMove;
5164         }
5165         break;
5166
5167       case IcsPlayingWhite:
5168         /* User is moving for White */
5169         if (!WhiteOnMove(currentMove)) {
5170             if (!appData.premove) {
5171                 DisplayMoveError(_("It is Black's turn"));
5172             } else if (toX >= 0 && toY >= 0) {
5173                 premoveToX = toX;
5174                 premoveToY = toY;
5175                 premoveFromX = fromX;
5176                 premoveFromY = fromY;
5177                 premovePromoChar = promoChar;
5178                 gotPremove = 1;
5179                 if (appData.debugMode) 
5180                     fprintf(debugFP, "Got premove: fromX %d,"
5181                             "fromY %d, toX %d, toY %d\n",
5182                             fromX, fromY, toX, toY);
5183             }
5184             return ImpossibleMove;
5185         }
5186         break;
5187
5188       default:
5189         break;
5190
5191       case EditPosition:
5192         /* EditPosition, empty square, or different color piece;
5193            click-click move is possible */
5194         if (toX == -2 || toY == -2) {
5195             boards[0][fromY][fromX] = EmptySquare;
5196             return AmbiguousMove;
5197         } else if (toX >= 0 && toY >= 0) {
5198             boards[0][toY][toX] = boards[0][fromY][fromX];
5199             boards[0][fromY][fromX] = EmptySquare;
5200             return AmbiguousMove;
5201         }
5202         return ImpossibleMove;
5203     }
5204
5205     /* [HGM] If move started in holdings, it means a drop */
5206     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5207          if( pup != EmptySquare ) return ImpossibleMove;
5208          if(appData.testLegality) {
5209              /* it would be more logical if LegalityTest() also figured out
5210               * which drops are legal. For now we forbid pawns on back rank.
5211               * Shogi is on its own here...
5212               */
5213              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5214                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5215                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5216          }
5217          return WhiteDrop; /* Not needed to specify white or black yet */
5218     }
5219
5220     userOfferedDraw = FALSE;
5221         
5222     /* [HGM] always test for legality, to get promotion info */
5223     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5224                           epStatus[currentMove], castlingRights[currentMove],
5225                                          fromY, fromX, toY, toX, promoChar);
5226
5227     /* [HGM] but possibly ignore an IllegalMove result */
5228     if (appData.testLegality) {
5229         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5230             DisplayMoveError(_("Illegal move"));
5231             return ImpossibleMove;
5232         }
5233     }
5234 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5235     return moveType;
5236     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5237        function is made into one that returns an OK move type if FinishMove
5238        should be called. This to give the calling driver routine the
5239        opportunity to finish the userMove input with a promotion popup,
5240        without bothering the user with this for invalid or illegal moves */
5241
5242 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5243 }
5244
5245 /* Common tail of UserMoveEvent and DropMenuEvent */
5246 int
5247 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5248      ChessMove moveType;
5249      int fromX, fromY, toX, toY;
5250      /*char*/int promoChar;
5251 {
5252     char *bookHit = 0;
5253 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5254     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5255         // [HGM] superchess: suppress promotions to non-available piece
5256         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5257         if(WhiteOnMove(currentMove)) {
5258             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5259         } else {
5260             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5261         }
5262     }
5263
5264     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5265        move type in caller when we know the move is a legal promotion */
5266     if(moveType == NormalMove && promoChar)
5267         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5268 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5269     /* [HGM] convert drag-and-drop piece drops to standard form */
5270     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5271          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5272            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5273                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5274 //         fromX = boards[currentMove][fromY][fromX];
5275            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5276            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5277            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5278            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5279          fromY = DROP_RANK;
5280     }
5281
5282     /* [HGM] <popupFix> The following if has been moved here from
5283        UserMoveEvent(). Because it seemed to belon here (why not allow
5284        piece drops in training games?), and because it can only be
5285        performed after it is known to what we promote. */
5286     if (gameMode == Training) {
5287       /* compare the move played on the board to the next move in the
5288        * game. If they match, display the move and the opponent's response. 
5289        * If they don't match, display an error message.
5290        */
5291       int saveAnimate;
5292       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5293       CopyBoard(testBoard, boards[currentMove]);
5294       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5295
5296       if (CompareBoards(testBoard, boards[currentMove+1])) {
5297         ForwardInner(currentMove+1);
5298
5299         /* Autoplay the opponent's response.
5300          * if appData.animate was TRUE when Training mode was entered,
5301          * the response will be animated.
5302          */
5303         saveAnimate = appData.animate;
5304         appData.animate = animateTraining;
5305         ForwardInner(currentMove+1);
5306         appData.animate = saveAnimate;
5307
5308         /* check for the end of the game */
5309         if (currentMove >= forwardMostMove) {
5310           gameMode = PlayFromGameFile;
5311           ModeHighlight();
5312           SetTrainingModeOff();
5313           DisplayInformation(_("End of game"));
5314         }
5315       } else {
5316         DisplayError(_("Incorrect move"), 0);
5317       }
5318       return 1;
5319     }
5320
5321   /* Ok, now we know that the move is good, so we can kill
5322      the previous line in Analysis Mode */
5323   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5324     forwardMostMove = currentMove;
5325   }
5326
5327   /* If we need the chess program but it's dead, restart it */
5328   ResurrectChessProgram();
5329
5330   /* A user move restarts a paused game*/
5331   if (pausing)
5332     PauseEvent();
5333
5334   thinkOutput[0] = NULLCHAR;
5335
5336   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5337
5338   if (gameMode == BeginningOfGame) {
5339     if (appData.noChessProgram) {
5340       gameMode = EditGame;
5341       SetGameInfo();
5342     } else {
5343       char buf[MSG_SIZ];
5344       gameMode = MachinePlaysBlack;
5345       StartClocks();
5346       SetGameInfo();
5347       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5348       DisplayTitle(buf);
5349       if (first.sendName) {
5350         sprintf(buf, "name %s\n", gameInfo.white);
5351         SendToProgram(buf, &first);
5352       }
5353       StartClocks();
5354     }
5355     ModeHighlight();
5356   }
5357 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5358   /* Relay move to ICS or chess engine */
5359   if (appData.icsActive) {
5360     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5361         gameMode == IcsExamining) {
5362       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5363       ics_user_moved = 1;
5364     }
5365   } else {
5366     if (first.sendTime && (gameMode == BeginningOfGame ||
5367                            gameMode == MachinePlaysWhite ||
5368                            gameMode == MachinePlaysBlack)) {
5369       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5370     }
5371     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5372          // [HGM] book: if program might be playing, let it use book
5373         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5374         first.maybeThinking = TRUE;
5375     } else SendMoveToProgram(forwardMostMove-1, &first);
5376     if (currentMove == cmailOldMove + 1) {
5377       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5378     }
5379   }
5380
5381   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5382
5383   switch (gameMode) {
5384   case EditGame:
5385     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5386                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5387     case MT_NONE:
5388     case MT_CHECK:
5389       break;
5390     case MT_CHECKMATE:
5391     case MT_STAINMATE:
5392       if (WhiteOnMove(currentMove)) {
5393         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5394       } else {
5395         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5396       }
5397       break;
5398     case MT_STALEMATE:
5399       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5400       break;
5401     }
5402     break;
5403     
5404   case MachinePlaysBlack:
5405   case MachinePlaysWhite:
5406     /* disable certain menu options while machine is thinking */
5407     SetMachineThinkingEnables();
5408     break;
5409
5410   default:
5411     break;
5412   }
5413
5414   if(bookHit) { // [HGM] book: simulate book reply
5415         static char bookMove[MSG_SIZ]; // a bit generous?
5416
5417         programStats.nodes = programStats.depth = programStats.time = 
5418         programStats.score = programStats.got_only_move = 0;
5419         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5420
5421         strcpy(bookMove, "move ");
5422         strcat(bookMove, bookHit);
5423         HandleMachineMove(bookMove, &first);
5424   }
5425   return 1;
5426 }
5427
5428 void
5429 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5430      int fromX, fromY, toX, toY;
5431      int promoChar;
5432 {
5433     /* [HGM] This routine was added to allow calling of its two logical
5434        parts from other modules in the old way. Before, UserMoveEvent()
5435        automatically called FinishMove() if the move was OK, and returned
5436        otherwise. I separated the two, in order to make it possible to
5437        slip a promotion popup in between. But that it always needs two
5438        calls, to the first part, (now called UserMoveTest() ), and to
5439        FinishMove if the first part succeeded. Calls that do not need
5440        to do anything in between, can call this routine the old way. 
5441     */
5442     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
5443 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5444     if(moveType == AmbiguousMove)
5445         DrawPosition(FALSE, boards[currentMove]);
5446     else if(moveType != ImpossibleMove)
5447         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5448 }
5449
5450 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5451 {
5452 //    char * hint = lastHint;
5453     FrontEndProgramStats stats;
5454
5455     stats.which = cps == &first ? 0 : 1;
5456     stats.depth = cpstats->depth;
5457     stats.nodes = cpstats->nodes;
5458     stats.score = cpstats->score;
5459     stats.time = cpstats->time;
5460     stats.pv = cpstats->movelist;
5461     stats.hint = lastHint;
5462     stats.an_move_index = 0;
5463     stats.an_move_count = 0;
5464
5465     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5466         stats.hint = cpstats->move_name;
5467         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5468         stats.an_move_count = cpstats->nr_moves;
5469     }
5470
5471     SetProgramStats( &stats );
5472 }
5473
5474 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5475 {   // [HGM] book: this routine intercepts moves to simulate book replies
5476     char *bookHit = NULL;
5477
5478     //first determine if the incoming move brings opponent into his book
5479     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5480         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5481     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5482     if(bookHit != NULL && !cps->bookSuspend) {
5483         // make sure opponent is not going to reply after receiving move to book position
5484         SendToProgram("force\n", cps);
5485         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5486     }
5487     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5488     // now arrange restart after book miss
5489     if(bookHit) {
5490         // after a book hit we never send 'go', and the code after the call to this routine
5491         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5492         char buf[MSG_SIZ];
5493         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5494         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5495         SendToProgram(buf, cps);
5496         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5497     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5498         SendToProgram("go\n", cps);
5499         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5500     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5501         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5502             SendToProgram("go\n", cps); 
5503         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5504     }
5505     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5506 }
5507
5508 char *savedMessage;
5509 ChessProgramState *savedState;
5510 void DeferredBookMove(void)
5511 {
5512         if(savedState->lastPing != savedState->lastPong)
5513                     ScheduleDelayedEvent(DeferredBookMove, 10);
5514         else
5515         HandleMachineMove(savedMessage, savedState);
5516 }
5517
5518 void
5519 HandleMachineMove(message, cps)
5520      char *message;
5521      ChessProgramState *cps;
5522 {
5523     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5524     char realname[MSG_SIZ];
5525     int fromX, fromY, toX, toY;
5526     ChessMove moveType;
5527     char promoChar;
5528     char *p;
5529     int machineWhite;
5530     char *bookHit;
5531
5532 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5533     /*
5534      * Kludge to ignore BEL characters
5535      */
5536     while (*message == '\007') message++;
5537
5538     /*
5539      * [HGM] engine debug message: ignore lines starting with '#' character
5540      */
5541     if(cps->debug && *message == '#') return;
5542
5543     /*
5544      * Look for book output
5545      */
5546     if (cps == &first && bookRequested) {
5547         if (message[0] == '\t' || message[0] == ' ') {
5548             /* Part of the book output is here; append it */
5549             strcat(bookOutput, message);
5550             strcat(bookOutput, "  \n");
5551             return;
5552         } else if (bookOutput[0] != NULLCHAR) {
5553             /* All of book output has arrived; display it */
5554             char *p = bookOutput;
5555             while (*p != NULLCHAR) {
5556                 if (*p == '\t') *p = ' ';
5557                 p++;
5558             }
5559             DisplayInformation(bookOutput);
5560             bookRequested = FALSE;
5561             /* Fall through to parse the current output */
5562         }
5563     }
5564
5565     /*
5566      * Look for machine move.
5567      */
5568     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5569         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5570     {
5571         /* This method is only useful on engines that support ping */
5572         if (cps->lastPing != cps->lastPong) {
5573           if (gameMode == BeginningOfGame) {
5574             /* Extra move from before last new; ignore */
5575             if (appData.debugMode) {
5576                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5577             }
5578           } else {
5579             if (appData.debugMode) {
5580                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5581                         cps->which, gameMode);
5582             }
5583
5584             SendToProgram("undo\n", cps);
5585           }
5586           return;
5587         }
5588
5589         switch (gameMode) {
5590           case BeginningOfGame:
5591             /* Extra move from before last reset; ignore */
5592             if (appData.debugMode) {
5593                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5594             }
5595             return;
5596
5597           case EndOfGame:
5598           case IcsIdle:
5599           default:
5600             /* Extra move after we tried to stop.  The mode test is
5601                not a reliable way of detecting this problem, but it's
5602                the best we can do on engines that don't support ping.
5603             */
5604             if (appData.debugMode) {
5605                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5606                         cps->which, gameMode);
5607             }
5608             SendToProgram("undo\n", cps);
5609             return;
5610
5611           case MachinePlaysWhite:
5612           case IcsPlayingWhite:
5613             machineWhite = TRUE;
5614             break;
5615
5616           case MachinePlaysBlack:
5617           case IcsPlayingBlack:
5618             machineWhite = FALSE;
5619             break;
5620
5621           case TwoMachinesPlay:
5622             machineWhite = (cps->twoMachinesColor[0] == 'w');
5623             break;
5624         }
5625         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5626             if (appData.debugMode) {
5627                 fprintf(debugFP,
5628                         "Ignoring move out of turn by %s, gameMode %d"
5629                         ", forwardMost %d\n",
5630                         cps->which, gameMode, forwardMostMove);
5631             }
5632             return;
5633         }
5634
5635     if (appData.debugMode) { int f = forwardMostMove;
5636         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5637                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5638     }
5639         if(cps->alphaRank) AlphaRank(machineMove, 4);
5640         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5641                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5642             /* Machine move could not be parsed; ignore it. */
5643             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5644                     machineMove, cps->which);
5645             DisplayError(buf1, 0);
5646             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5647                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5648             if (gameMode == TwoMachinesPlay) {
5649               GameEnds(machineWhite ? BlackWins : WhiteWins,
5650                        buf1, GE_XBOARD);
5651             }
5652             return;
5653         }
5654
5655         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5656         /* So we have to redo legality test with true e.p. status here,  */
5657         /* to make sure an illegal e.p. capture does not slip through,   */
5658         /* to cause a forfeit on a justified illegal-move complaint      */
5659         /* of the opponent.                                              */
5660         if( gameMode==TwoMachinesPlay && appData.testLegality
5661             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5662                                                               ) {
5663            ChessMove moveType;
5664            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5665                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5666                              fromY, fromX, toY, toX, promoChar);
5667             if (appData.debugMode) {
5668                 int i;
5669                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5670                     castlingRights[forwardMostMove][i], castlingRank[i]);
5671                 fprintf(debugFP, "castling rights\n");
5672             }
5673             if(moveType == IllegalMove) {
5674                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5675                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5676                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5677                            buf1, GE_XBOARD);
5678                 return;
5679            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5680            /* [HGM] Kludge to handle engines that send FRC-style castling
5681               when they shouldn't (like TSCP-Gothic) */
5682            switch(moveType) {
5683              case WhiteASideCastleFR:
5684              case BlackASideCastleFR:
5685                toX+=2;
5686                currentMoveString[2]++;
5687                break;
5688              case WhiteHSideCastleFR:
5689              case BlackHSideCastleFR:
5690                toX--;
5691                currentMoveString[2]--;
5692                break;
5693              default: ; // nothing to do, but suppresses warning of pedantic compilers
5694            }
5695         }
5696         hintRequested = FALSE;
5697         lastHint[0] = NULLCHAR;
5698         bookRequested = FALSE;
5699         /* Program may be pondering now */
5700         cps->maybeThinking = TRUE;
5701         if (cps->sendTime == 2) cps->sendTime = 1;
5702         if (cps->offeredDraw) cps->offeredDraw--;
5703
5704 #if ZIPPY
5705         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5706             first.initDone) {
5707           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5708           ics_user_moved = 1;
5709           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5710                 char buf[3*MSG_SIZ];
5711
5712                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5713                         programStats.score / 100.,
5714                         programStats.depth,
5715                         programStats.time / 100.,
5716                         (unsigned int)programStats.nodes,
5717                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5718                         programStats.movelist);
5719                 SendToICS(buf);
5720 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5721           }
5722         }
5723 #endif
5724         /* currentMoveString is set as a side-effect of ParseOneMove */
5725         strcpy(machineMove, currentMoveString);
5726         strcat(machineMove, "\n");
5727         strcpy(moveList[forwardMostMove], machineMove);
5728
5729         /* [AS] Save move info and clear stats for next move */
5730         pvInfoList[ forwardMostMove ].score = programStats.score;
5731         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5732         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5733         ClearProgramStats();
5734         thinkOutput[0] = NULLCHAR;
5735         hiddenThinkOutputState = 0;
5736
5737         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5738
5739         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5740         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5741             int count = 0;
5742
5743             while( count < adjudicateLossPlies ) {
5744                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5745
5746                 if( count & 1 ) {
5747                     score = -score; /* Flip score for winning side */
5748                 }
5749
5750                 if( score > adjudicateLossThreshold ) {
5751                     break;
5752                 }
5753
5754                 count++;
5755             }
5756
5757             if( count >= adjudicateLossPlies ) {
5758                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5759
5760                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5761                     "Xboard adjudication", 
5762                     GE_XBOARD );
5763
5764                 return;
5765             }
5766         }
5767
5768         if( gameMode == TwoMachinesPlay ) {
5769           // [HGM] some adjudications useful with buggy engines
5770             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5771           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5772
5773
5774             if( appData.testLegality )
5775             {   /* [HGM] Some more adjudications for obstinate engines */
5776                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5777                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5778                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5779                 static int moveCount = 6;
5780                 ChessMove result;
5781                 char *reason = NULL;
5782
5783                 /* Count what is on board. */
5784                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5785                 {   ChessSquare p = boards[forwardMostMove][i][j];
5786                     int m=i;
5787
5788                     switch((int) p)
5789                     {   /* count B,N,R and other of each side */
5790                         case WhiteKing:
5791                         case BlackKing:
5792                              NrK++; break; // [HGM] atomic: count Kings
5793                         case WhiteKnight:
5794                              NrWN++; break;
5795                         case WhiteBishop:
5796                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5797                              bishopsColor |= 1 << ((i^j)&1);
5798                              NrWB++; break;
5799                         case BlackKnight:
5800                              NrBN++; break;
5801                         case BlackBishop:
5802                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5803                              bishopsColor |= 1 << ((i^j)&1);
5804                              NrBB++; break;
5805                         case WhiteRook:
5806                              NrWR++; break;
5807                         case BlackRook:
5808                              NrBR++; break;
5809                         case WhiteQueen:
5810                              NrWQ++; break;
5811                         case BlackQueen:
5812                              NrBQ++; break;
5813                         case EmptySquare: 
5814                              break;
5815                         case BlackPawn:
5816                              m = 7-i;
5817                         case WhitePawn:
5818                              PawnAdvance += m; NrPawns++;
5819                     }
5820                     NrPieces += (p != EmptySquare);
5821                     NrW += ((int)p < (int)BlackPawn);
5822                     if(gameInfo.variant == VariantXiangqi && 
5823                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5824                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5825                         NrW -= ((int)p < (int)BlackPawn);
5826                     }
5827                 }
5828
5829                 /* Some material-based adjudications that have to be made before stalemate test */
5830                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5831                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5832                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5833                      if(appData.checkMates) {
5834                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5835                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5836                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
5837                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
5838                          return;
5839                      }
5840                 }
5841
5842                 /* Bare King in Shatranj (loses) or Losers (wins) */
5843                 if( NrW == 1 || NrPieces - NrW == 1) {
5844                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5845                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
5846                      if(appData.checkMates) {
5847                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5848                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5849                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5850                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5851                          return;
5852                      }
5853                   } else
5854                   if( gameInfo.variant == VariantShatranj && --bare < 0)
5855                   {    /* bare King */
5856                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5857                         if(appData.checkMates) {
5858                             /* but only adjudicate if adjudication enabled */
5859                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5860                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5861                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
5862                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5863                             return;
5864                         }
5865                   }
5866                 } else bare = 1;
5867
5868
5869             // don't wait for engine to announce game end if we can judge ourselves
5870             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5871                                        castlingRights[forwardMostMove]) ) {
5872               case MT_CHECK:
5873                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5874                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
5875                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5876                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5877                             checkCnt++;
5878                         if(checkCnt >= 2) {
5879                             reason = "Xboard adjudication: 3rd check";
5880                             epStatus[forwardMostMove] = EP_CHECKMATE;
5881                             break;
5882                         }
5883                     }
5884                 }
5885               case MT_NONE:
5886               default:
5887                 break;
5888               case MT_STALEMATE:
5889               case MT_STAINMATE:
5890                 reason = "Xboard adjudication: Stalemate";
5891                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5892                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
5893                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5894                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
5895                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5896                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5897                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5898                                                                         EP_CHECKMATE : EP_WINS);
5899                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5900                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5901                 }
5902                 break;
5903               case MT_CHECKMATE:
5904                 reason = "Xboard adjudication: Checkmate";
5905                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5906                 break;
5907             }
5908
5909                 switch(i = epStatus[forwardMostMove]) {
5910                     case EP_STALEMATE:
5911                         result = GameIsDrawn; break;
5912                     case EP_CHECKMATE:
5913                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5914                     case EP_WINS:
5915                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5916                     default:
5917                         result = (ChessMove) 0;
5918                 }
5919                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5920                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5921                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5922                     GameEnds( result, reason, GE_XBOARD );
5923                     return;
5924                 }
5925
5926                 /* Next absolutely insufficient mating material. */
5927                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
5928                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5929                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5930                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5931                 {    /* KBK, KNK, KK of KBKB with like Bishops */
5932
5933                      /* always flag draws, for judging claims */
5934                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
5935
5936                      if(appData.materialDraws) {
5937                          /* but only adjudicate them if adjudication enabled */
5938                          SendToProgram("force\n", cps->other); // suppress reply
5939                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5940                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5941                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5942                          return;
5943                      }
5944                 }
5945
5946                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5947                 if(NrPieces == 4 && 
5948                    (   NrWR == 1 && NrBR == 1 /* KRKR */
5949                    || NrWQ==1 && NrBQ==1     /* KQKQ */
5950                    || NrWN==2 || NrBN==2     /* KNNK */
5951                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5952                   ) ) {
5953                      if(--moveCount < 0 && appData.trivialDraws)
5954                      {    /* if the first 3 moves do not show a tactical win, declare draw */
5955                           SendToProgram("force\n", cps->other); // suppress reply
5956                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5957                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5958                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5959                           return;
5960                      }
5961                 } else moveCount = 6;
5962             }
5963           }
5964 #if 1
5965     if (appData.debugMode) { int i;
5966       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5967               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5968               appData.drawRepeats);
5969       for( i=forwardMostMove; i>=backwardMostMove; i-- )
5970            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5971
5972     }
5973 #endif
5974                 /* Check for rep-draws */
5975                 count = 0;
5976                 for(k = forwardMostMove-2;
5977                     k>=backwardMostMove && k>=forwardMostMove-100 &&
5978                         epStatus[k] < EP_UNKNOWN &&
5979                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5980                     k-=2)
5981                 {   int rights=0;
5982 #if 0
5983     if (appData.debugMode) {
5984       fprintf(debugFP, " loop\n");
5985     }
5986 #endif
5987                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
5988 #if 0
5989     if (appData.debugMode) {
5990       fprintf(debugFP, "match\n");
5991     }
5992 #endif
5993                         /* compare castling rights */
5994                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5995                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5996                                 rights++; /* King lost rights, while rook still had them */
5997                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5998                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5999                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6000                                    rights++; /* but at least one rook lost them */
6001                         }
6002                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6003                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6004                                 rights++; 
6005                         if( castlingRights[forwardMostMove][5] >= 0 ) {
6006                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6007                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6008                                    rights++;
6009                         }
6010 #if 0
6011     if (appData.debugMode) {
6012       for(i=0; i<nrCastlingRights; i++)
6013       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
6014     }
6015
6016     if (appData.debugMode) {
6017       fprintf(debugFP, " %d %d\n", rights, k);
6018     }
6019 #endif
6020                         if( rights == 0 && ++count > appData.drawRepeats-2
6021                             && appData.drawRepeats > 1) {
6022                              /* adjudicate after user-specified nr of repeats */
6023                              SendToProgram("force\n", cps->other); // suppress reply
6024                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6025                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6026                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6027                                 // [HGM] xiangqi: check for forbidden perpetuals
6028                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6029                                 for(m=forwardMostMove; m>k; m-=2) {
6030                                     if(MateTest(boards[m], PosFlags(m), 
6031                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
6032                                         ourPerpetual = 0; // the current mover did not always check
6033                                     if(MateTest(boards[m-1], PosFlags(m-1), 
6034                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
6035                                         hisPerpetual = 0; // the opponent did not always check
6036                                 }
6037                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6038                                                                         ourPerpetual, hisPerpetual);
6039                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6040                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6041                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6042                                     return;
6043                                 }
6044                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6045                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6046                                 // Now check for perpetual chases
6047                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6048                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6049                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6050                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6051                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6052                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6053                                         return;
6054                                     }
6055                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6056                                         break; // Abort repetition-checking loop.
6057                                 }
6058                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6059                              }
6060                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6061                              return;
6062                         }
6063                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6064                              epStatus[forwardMostMove] = EP_REP_DRAW;
6065                     }
6066                 }
6067
6068                 /* Now we test for 50-move draws. Determine ply count */
6069                 count = forwardMostMove;
6070                 /* look for last irreversble move */
6071                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6072                     count--;
6073                 /* if we hit starting position, add initial plies */
6074                 if( count == backwardMostMove )
6075                     count -= initialRulePlies;
6076                 count = forwardMostMove - count; 
6077                 if( count >= 100)
6078                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6079                          /* this is used to judge if draw claims are legal */
6080                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6081                          SendToProgram("force\n", cps->other); // suppress reply
6082                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6083                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6084                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6085                          return;
6086                 }
6087
6088                 /* if draw offer is pending, treat it as a draw claim
6089                  * when draw condition present, to allow engines a way to
6090                  * claim draws before making their move to avoid a race
6091                  * condition occurring after their move
6092                  */
6093                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6094                          char *p = NULL;
6095                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6096                              p = "Draw claim: 50-move rule";
6097                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6098                              p = "Draw claim: 3-fold repetition";
6099                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6100                              p = "Draw claim: insufficient mating material";
6101                          if( p != NULL ) {
6102                              SendToProgram("force\n", cps->other); // suppress reply
6103                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6104                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6105                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6106                              return;
6107                          }
6108                 }
6109
6110
6111                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6112                     SendToProgram("force\n", cps->other); // suppress reply
6113                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6114                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6115
6116                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6117
6118                     return;
6119                 }
6120         }
6121
6122         bookHit = NULL;
6123         if (gameMode == TwoMachinesPlay) {
6124             /* [HGM] relaying draw offers moved to after reception of move */
6125             /* and interpreting offer as claim if it brings draw condition */
6126             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6127                 SendToProgram("draw\n", cps->other);
6128             }
6129             if (cps->other->sendTime) {
6130                 SendTimeRemaining(cps->other,
6131                                   cps->other->twoMachinesColor[0] == 'w');
6132             }
6133             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6134             if (firstMove && !bookHit) {
6135                 firstMove = FALSE;
6136                 if (cps->other->useColors) {
6137                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6138                 }
6139                 SendToProgram("go\n", cps->other);
6140             }
6141             cps->other->maybeThinking = TRUE;
6142         }
6143
6144         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6145         
6146         if (!pausing && appData.ringBellAfterMoves) {
6147             RingBell();
6148         }
6149
6150         /* 
6151          * Reenable menu items that were disabled while
6152          * machine was thinking
6153          */
6154         if (gameMode != TwoMachinesPlay)
6155             SetUserThinkingEnables();
6156
6157         // [HGM] book: after book hit opponent has received move and is now in force mode
6158         // force the book reply into it, and then fake that it outputted this move by jumping
6159         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6160         if(bookHit) {
6161                 static char bookMove[MSG_SIZ]; // a bit generous?
6162
6163                 strcpy(bookMove, "move ");
6164                 strcat(bookMove, bookHit);
6165                 message = bookMove;
6166                 cps = cps->other;
6167                 programStats.nodes = programStats.depth = programStats.time = 
6168                 programStats.score = programStats.got_only_move = 0;
6169                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6170
6171                 if(cps->lastPing != cps->lastPong) {
6172                     savedMessage = message; // args for deferred call
6173                     savedState = cps;
6174                     ScheduleDelayedEvent(DeferredBookMove, 10);
6175                     return;
6176                 }
6177                 goto FakeBookMove;
6178         }
6179
6180         return;
6181     }
6182
6183     /* Set special modes for chess engines.  Later something general
6184      *  could be added here; for now there is just one kludge feature,
6185      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6186      *  when "xboard" is given as an interactive command.
6187      */
6188     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6189         cps->useSigint = FALSE;
6190         cps->useSigterm = FALSE;
6191     }
6192     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6193       ParseFeatures(message+8, cps);
6194       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6195     }
6196
6197     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6198      * want this, I was asked to put it in, and obliged.
6199      */
6200     if (!strncmp(message, "setboard ", 9)) {
6201         Board initial_position; int i;
6202
6203         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6204
6205         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6206             DisplayError(_("Bad FEN received from engine"), 0);
6207             return ;
6208         } else {
6209            Reset(FALSE, FALSE);
6210            CopyBoard(boards[0], initial_position);
6211            initialRulePlies = FENrulePlies;
6212            epStatus[0] = FENepStatus;
6213            for( i=0; i<nrCastlingRights; i++ )
6214                 castlingRights[0][i] = FENcastlingRights[i];
6215            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6216            else gameMode = MachinePlaysBlack;                 
6217            DrawPosition(FALSE, boards[currentMove]);
6218         }
6219         return;
6220     }
6221
6222     /*
6223      * Look for communication commands
6224      */
6225     if (!strncmp(message, "telluser ", 9)) {
6226         DisplayNote(message + 9);
6227         return;
6228     }
6229     if (!strncmp(message, "tellusererror ", 14)) {
6230         DisplayError(message + 14, 0);
6231         return;
6232     }
6233     if (!strncmp(message, "tellopponent ", 13)) {
6234       if (appData.icsActive) {
6235         if (loggedOn) {
6236           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6237           SendToICS(buf1);
6238         }
6239       } else {
6240         DisplayNote(message + 13);
6241       }
6242       return;
6243     }
6244     if (!strncmp(message, "tellothers ", 11)) {
6245       if (appData.icsActive) {
6246         if (loggedOn) {
6247           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6248           SendToICS(buf1);
6249         }
6250       }
6251       return;
6252     }
6253     if (!strncmp(message, "tellall ", 8)) {
6254       if (appData.icsActive) {
6255         if (loggedOn) {
6256           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6257           SendToICS(buf1);
6258         }
6259       } else {
6260         DisplayNote(message + 8);
6261       }
6262       return;
6263     }
6264     if (strncmp(message, "warning", 7) == 0) {
6265         /* Undocumented feature, use tellusererror in new code */
6266         DisplayError(message, 0);
6267         return;
6268     }
6269     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6270         strcpy(realname, cps->tidy);
6271         strcat(realname, " query");
6272         AskQuestion(realname, buf2, buf1, cps->pr);
6273         return;
6274     }
6275     /* Commands from the engine directly to ICS.  We don't allow these to be 
6276      *  sent until we are logged on. Crafty kibitzes have been known to 
6277      *  interfere with the login process.
6278      */
6279     if (loggedOn) {
6280         if (!strncmp(message, "tellics ", 8)) {
6281             SendToICS(message + 8);
6282             SendToICS("\n");
6283             return;
6284         }
6285         if (!strncmp(message, "tellicsnoalias ", 15)) {
6286             SendToICS(ics_prefix);
6287             SendToICS(message + 15);
6288             SendToICS("\n");
6289             return;
6290         }
6291         /* The following are for backward compatibility only */
6292         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6293             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6294             SendToICS(ics_prefix);
6295             SendToICS(message);
6296             SendToICS("\n");
6297             return;
6298         }
6299     }
6300     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6301         return;
6302     }
6303     /*
6304      * If the move is illegal, cancel it and redraw the board.
6305      * Also deal with other error cases.  Matching is rather loose
6306      * here to accommodate engines written before the spec.
6307      */
6308     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6309         strncmp(message, "Error", 5) == 0) {
6310         if (StrStr(message, "name") || 
6311             StrStr(message, "rating") || StrStr(message, "?") ||
6312             StrStr(message, "result") || StrStr(message, "board") ||
6313             StrStr(message, "bk") || StrStr(message, "computer") ||
6314             StrStr(message, "variant") || StrStr(message, "hint") ||
6315             StrStr(message, "random") || StrStr(message, "depth") ||
6316             StrStr(message, "accepted")) {
6317             return;
6318         }
6319         if (StrStr(message, "protover")) {
6320           /* Program is responding to input, so it's apparently done
6321              initializing, and this error message indicates it is
6322              protocol version 1.  So we don't need to wait any longer
6323              for it to initialize and send feature commands. */
6324           FeatureDone(cps, 1);
6325           cps->protocolVersion = 1;
6326           return;
6327         }
6328         cps->maybeThinking = FALSE;
6329
6330         if (StrStr(message, "draw")) {
6331             /* Program doesn't have "draw" command */
6332             cps->sendDrawOffers = 0;
6333             return;
6334         }
6335         if (cps->sendTime != 1 &&
6336             (StrStr(message, "time") || StrStr(message, "otim"))) {
6337           /* Program apparently doesn't have "time" or "otim" command */
6338           cps->sendTime = 0;
6339           return;
6340         }
6341         if (StrStr(message, "analyze")) {
6342             cps->analysisSupport = FALSE;
6343             cps->analyzing = FALSE;
6344             Reset(FALSE, TRUE);
6345             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6346             DisplayError(buf2, 0);
6347             return;
6348         }
6349         if (StrStr(message, "(no matching move)st")) {
6350           /* Special kludge for GNU Chess 4 only */
6351           cps->stKludge = TRUE;
6352           SendTimeControl(cps, movesPerSession, timeControl,
6353                           timeIncrement, appData.searchDepth,
6354                           searchTime);
6355           return;
6356         }
6357         if (StrStr(message, "(no matching move)sd")) {
6358           /* Special kludge for GNU Chess 4 only */
6359           cps->sdKludge = TRUE;
6360           SendTimeControl(cps, movesPerSession, timeControl,
6361                           timeIncrement, appData.searchDepth,
6362                           searchTime);
6363           return;
6364         }
6365         if (!StrStr(message, "llegal")) {
6366             return;
6367         }
6368         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6369             gameMode == IcsIdle) return;
6370         if (forwardMostMove <= backwardMostMove) return;
6371 #if 0
6372         /* Following removed: it caused a bug where a real illegal move
6373            message in analyze mored would be ignored. */
6374         if (cps == &first && programStats.ok_to_send == 0) {
6375             /* Bogus message from Crafty responding to "."  This filtering
6376                can miss some of the bad messages, but fortunately the bug 
6377                is fixed in current Crafty versions, so it doesn't matter. */
6378             return;
6379         }
6380 #endif
6381         if (pausing) PauseEvent();
6382         if (gameMode == PlayFromGameFile) {
6383             /* Stop reading this game file */
6384             gameMode = EditGame;
6385             ModeHighlight();
6386         }
6387         currentMove = --forwardMostMove;
6388         DisplayMove(currentMove-1); /* before DisplayMoveError */
6389         SwitchClocks();
6390         DisplayBothClocks();
6391         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6392                 parseList[currentMove], cps->which);
6393         DisplayMoveError(buf1);
6394         DrawPosition(FALSE, boards[currentMove]);
6395
6396         /* [HGM] illegal-move claim should forfeit game when Xboard */
6397         /* only passes fully legal moves                            */
6398         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6399             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6400                                 "False illegal-move claim", GE_XBOARD );
6401         }
6402         return;
6403     }
6404     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6405         /* Program has a broken "time" command that
6406            outputs a string not ending in newline.
6407            Don't use it. */
6408         cps->sendTime = 0;
6409     }
6410     
6411     /*
6412      * If chess program startup fails, exit with an error message.
6413      * Attempts to recover here are futile.
6414      */
6415     if ((StrStr(message, "unknown host") != NULL)
6416         || (StrStr(message, "No remote directory") != NULL)
6417         || (StrStr(message, "not found") != NULL)
6418         || (StrStr(message, "No such file") != NULL)
6419         || (StrStr(message, "can't alloc") != NULL)
6420         || (StrStr(message, "Permission denied") != NULL)) {
6421
6422         cps->maybeThinking = FALSE;
6423         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6424                 cps->which, cps->program, cps->host, message);
6425         RemoveInputSource(cps->isr);
6426         DisplayFatalError(buf1, 0, 1);
6427         return;
6428     }
6429     
6430     /* 
6431      * Look for hint output
6432      */
6433     if (sscanf(message, "Hint: %s", buf1) == 1) {
6434         if (cps == &first && hintRequested) {
6435             hintRequested = FALSE;
6436             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6437                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6438                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6439                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6440                                     fromY, fromX, toY, toX, promoChar, buf1);
6441                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6442                 DisplayInformation(buf2);
6443             } else {
6444                 /* Hint move could not be parsed!? */
6445               snprintf(buf2, sizeof(buf2),
6446                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6447                         buf1, cps->which);
6448                 DisplayError(buf2, 0);
6449             }
6450         } else {
6451             strcpy(lastHint, buf1);
6452         }
6453         return;
6454     }
6455
6456     /*
6457      * Ignore other messages if game is not in progress
6458      */
6459     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6460         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6461
6462     /*
6463      * look for win, lose, draw, or draw offer
6464      */
6465     if (strncmp(message, "1-0", 3) == 0) {
6466         char *p, *q, *r = "";
6467         p = strchr(message, '{');
6468         if (p) {
6469             q = strchr(p, '}');
6470             if (q) {
6471                 *q = NULLCHAR;
6472                 r = p + 1;
6473             }
6474         }
6475         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6476         return;
6477     } else if (strncmp(message, "0-1", 3) == 0) {
6478         char *p, *q, *r = "";
6479         p = strchr(message, '{');
6480         if (p) {
6481             q = strchr(p, '}');
6482             if (q) {
6483                 *q = NULLCHAR;
6484                 r = p + 1;
6485             }
6486         }
6487         /* Kludge for Arasan 4.1 bug */
6488         if (strcmp(r, "Black resigns") == 0) {
6489             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6490             return;
6491         }
6492         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6493         return;
6494     } else if (strncmp(message, "1/2", 3) == 0) {
6495         char *p, *q, *r = "";
6496         p = strchr(message, '{');
6497         if (p) {
6498             q = strchr(p, '}');
6499             if (q) {
6500                 *q = NULLCHAR;
6501                 r = p + 1;
6502             }
6503         }
6504             
6505         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6506         return;
6507
6508     } else if (strncmp(message, "White resign", 12) == 0) {
6509         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6510         return;
6511     } else if (strncmp(message, "Black resign", 12) == 0) {
6512         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6513         return;
6514     } else if (strncmp(message, "White matches", 13) == 0 ||
6515                strncmp(message, "Black matches", 13) == 0   ) {
6516         /* [HGM] ignore GNUShogi noises */
6517         return;
6518     } else if (strncmp(message, "White", 5) == 0 &&
6519                message[5] != '(' &&
6520                StrStr(message, "Black") == NULL) {
6521         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6522         return;
6523     } else if (strncmp(message, "Black", 5) == 0 &&
6524                message[5] != '(') {
6525         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6526         return;
6527     } else if (strcmp(message, "resign") == 0 ||
6528                strcmp(message, "computer resigns") == 0) {
6529         switch (gameMode) {
6530           case MachinePlaysBlack:
6531           case IcsPlayingBlack:
6532             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6533             break;
6534           case MachinePlaysWhite:
6535           case IcsPlayingWhite:
6536             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6537             break;
6538           case TwoMachinesPlay:
6539             if (cps->twoMachinesColor[0] == 'w')
6540               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6541             else
6542               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6543             break;
6544           default:
6545             /* can't happen */
6546             break;
6547         }
6548         return;
6549     } else if (strncmp(message, "opponent mates", 14) == 0) {
6550         switch (gameMode) {
6551           case MachinePlaysBlack:
6552           case IcsPlayingBlack:
6553             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6554             break;
6555           case MachinePlaysWhite:
6556           case IcsPlayingWhite:
6557             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6558             break;
6559           case TwoMachinesPlay:
6560             if (cps->twoMachinesColor[0] == 'w')
6561               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6562             else
6563               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6564             break;
6565           default:
6566             /* can't happen */
6567             break;
6568         }
6569         return;
6570     } else if (strncmp(message, "computer mates", 14) == 0) {
6571         switch (gameMode) {
6572           case MachinePlaysBlack:
6573           case IcsPlayingBlack:
6574             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6575             break;
6576           case MachinePlaysWhite:
6577           case IcsPlayingWhite:
6578             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6579             break;
6580           case TwoMachinesPlay:
6581             if (cps->twoMachinesColor[0] == 'w')
6582               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6583             else
6584               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6585             break;
6586           default:
6587             /* can't happen */
6588             break;
6589         }
6590         return;
6591     } else if (strncmp(message, "checkmate", 9) == 0) {
6592         if (WhiteOnMove(forwardMostMove)) {
6593             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6594         } else {
6595             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6596         }
6597         return;
6598     } else if (strstr(message, "Draw") != NULL ||
6599                strstr(message, "game is a draw") != NULL) {
6600         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6601         return;
6602     } else if (strstr(message, "offer") != NULL &&
6603                strstr(message, "draw") != NULL) {
6604 #if ZIPPY
6605         if (appData.zippyPlay && first.initDone) {
6606             /* Relay offer to ICS */
6607             SendToICS(ics_prefix);
6608             SendToICS("draw\n");
6609         }
6610 #endif
6611         cps->offeredDraw = 2; /* valid until this engine moves twice */
6612         if (gameMode == TwoMachinesPlay) {
6613             if (cps->other->offeredDraw) {
6614                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6615             /* [HGM] in two-machine mode we delay relaying draw offer      */
6616             /* until after we also have move, to see if it is really claim */
6617             }
6618 #if 0
6619               else {
6620                 if (cps->other->sendDrawOffers) {
6621                     SendToProgram("draw\n", cps->other);
6622                 }
6623             }
6624 #endif
6625         } else if (gameMode == MachinePlaysWhite ||
6626                    gameMode == MachinePlaysBlack) {
6627           if (userOfferedDraw) {
6628             DisplayInformation(_("Machine accepts your draw offer"));
6629             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6630           } else {
6631             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6632           }
6633         }
6634     }
6635
6636     
6637     /*
6638      * Look for thinking output
6639      */
6640     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6641           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6642                                 ) {
6643         int plylev, mvleft, mvtot, curscore, time;
6644         char mvname[MOVE_LEN];
6645         u64 nodes; // [DM]
6646         char plyext;
6647         int ignore = FALSE;
6648         int prefixHint = FALSE;
6649         mvname[0] = NULLCHAR;
6650
6651         switch (gameMode) {
6652           case MachinePlaysBlack:
6653           case IcsPlayingBlack:
6654             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6655             break;
6656           case MachinePlaysWhite:
6657           case IcsPlayingWhite:
6658             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6659             break;
6660           case AnalyzeMode:
6661           case AnalyzeFile:
6662             break;
6663           case IcsObserving: /* [DM] icsEngineAnalyze */
6664             if (!appData.icsEngineAnalyze) ignore = TRUE;
6665             break;
6666           case TwoMachinesPlay:
6667             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6668                 ignore = TRUE;
6669             }
6670             break;
6671           default:
6672             ignore = TRUE;
6673             break;
6674         }
6675
6676         if (!ignore) {
6677             buf1[0] = NULLCHAR;
6678             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6679                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6680
6681                 if (plyext != ' ' && plyext != '\t') {
6682                     time *= 100;
6683                 }
6684
6685                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6686                 if( cps->scoreIsAbsolute && 
6687                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6688                 {
6689                     curscore = -curscore;
6690                 }
6691
6692
6693                 programStats.depth = plylev;
6694                 programStats.nodes = nodes;
6695                 programStats.time = time;
6696                 programStats.score = curscore;
6697                 programStats.got_only_move = 0;
6698
6699                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6700                         int ticklen;
6701
6702                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6703                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6704                         if(WhiteOnMove(forwardMostMove)) 
6705                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6706                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6707                 }
6708
6709                 /* Buffer overflow protection */
6710                 if (buf1[0] != NULLCHAR) {
6711                     if (strlen(buf1) >= sizeof(programStats.movelist)
6712                         && appData.debugMode) {
6713                         fprintf(debugFP,
6714                                 "PV is too long; using the first %d bytes.\n",
6715                                 sizeof(programStats.movelist) - 1);
6716                     }
6717
6718                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6719                 } else {
6720                     sprintf(programStats.movelist, " no PV\n");
6721                 }
6722
6723                 if (programStats.seen_stat) {
6724                     programStats.ok_to_send = 1;
6725                 }
6726
6727                 if (strchr(programStats.movelist, '(') != NULL) {
6728                     programStats.line_is_book = 1;
6729                     programStats.nr_moves = 0;
6730                     programStats.moves_left = 0;
6731                 } else {
6732                     programStats.line_is_book = 0;
6733                 }
6734
6735                 SendProgramStatsToFrontend( cps, &programStats );
6736
6737                 /* 
6738                     [AS] Protect the thinkOutput buffer from overflow... this
6739                     is only useful if buf1 hasn't overflowed first!
6740                 */
6741                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6742                         plylev, 
6743                         (gameMode == TwoMachinesPlay ?
6744                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6745                         ((double) curscore) / 100.0,
6746                         prefixHint ? lastHint : "",
6747                         prefixHint ? " " : "" );
6748
6749                 if( buf1[0] != NULLCHAR ) {
6750                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6751
6752                     if( strlen(buf1) > max_len ) {
6753                         if( appData.debugMode) {
6754                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6755                         }
6756                         buf1[max_len+1] = '\0';
6757                     }
6758
6759                     strcat( thinkOutput, buf1 );
6760                 }
6761
6762                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6763                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6764                     DisplayMove(currentMove - 1);
6765                     DisplayAnalysis();
6766                 }
6767                 return;
6768
6769             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6770                 /* crafty (9.25+) says "(only move) <move>"
6771                  * if there is only 1 legal move
6772                  */
6773                 sscanf(p, "(only move) %s", buf1);
6774                 sprintf(thinkOutput, "%s (only move)", buf1);
6775                 sprintf(programStats.movelist, "%s (only move)", buf1);
6776                 programStats.depth = 1;
6777                 programStats.nr_moves = 1;
6778                 programStats.moves_left = 1;
6779                 programStats.nodes = 1;
6780                 programStats.time = 1;
6781                 programStats.got_only_move = 1;
6782
6783                 /* Not really, but we also use this member to
6784                    mean "line isn't going to change" (Crafty
6785                    isn't searching, so stats won't change) */
6786                 programStats.line_is_book = 1;
6787
6788                 SendProgramStatsToFrontend( cps, &programStats );
6789                 
6790                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
6791                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6792                     DisplayMove(currentMove - 1);
6793                     DisplayAnalysis();
6794                 }
6795                 return;
6796             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6797                               &time, &nodes, &plylev, &mvleft,
6798                               &mvtot, mvname) >= 5) {
6799                 /* The stat01: line is from Crafty (9.29+) in response
6800                    to the "." command */
6801                 programStats.seen_stat = 1;
6802                 cps->maybeThinking = TRUE;
6803
6804                 if (programStats.got_only_move || !appData.periodicUpdates)
6805                   return;
6806
6807                 programStats.depth = plylev;
6808                 programStats.time = time;
6809                 programStats.nodes = nodes;
6810                 programStats.moves_left = mvleft;
6811                 programStats.nr_moves = mvtot;
6812                 strcpy(programStats.move_name, mvname);
6813                 programStats.ok_to_send = 1;
6814                 programStats.movelist[0] = '\0';
6815
6816                 SendProgramStatsToFrontend( cps, &programStats );
6817
6818                 DisplayAnalysis();
6819                 return;
6820
6821             } else if (strncmp(message,"++",2) == 0) {
6822                 /* Crafty 9.29+ outputs this */
6823                 programStats.got_fail = 2;
6824                 return;
6825
6826             } else if (strncmp(message,"--",2) == 0) {
6827                 /* Crafty 9.29+ outputs this */
6828                 programStats.got_fail = 1;
6829                 return;
6830
6831             } else if (thinkOutput[0] != NULLCHAR &&
6832                        strncmp(message, "    ", 4) == 0) {
6833                 unsigned message_len;
6834
6835                 p = message;
6836                 while (*p && *p == ' ') p++;
6837
6838                 message_len = strlen( p );
6839
6840                 /* [AS] Avoid buffer overflow */
6841                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6842                     strcat(thinkOutput, " ");
6843                     strcat(thinkOutput, p);
6844                 }
6845
6846                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6847                     strcat(programStats.movelist, " ");
6848                     strcat(programStats.movelist, p);
6849                 }
6850
6851                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6852                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6853                     DisplayMove(currentMove - 1);
6854                     DisplayAnalysis();
6855                 }
6856                 return;
6857             }
6858         }
6859         else {
6860             buf1[0] = NULLCHAR;
6861
6862             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6863                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
6864             {
6865                 ChessProgramStats cpstats;
6866
6867                 if (plyext != ' ' && plyext != '\t') {
6868                     time *= 100;
6869                 }
6870
6871                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6872                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6873                     curscore = -curscore;
6874                 }
6875
6876                 cpstats.depth = plylev;
6877                 cpstats.nodes = nodes;
6878                 cpstats.time = time;
6879                 cpstats.score = curscore;
6880                 cpstats.got_only_move = 0;
6881                 cpstats.movelist[0] = '\0';
6882
6883                 if (buf1[0] != NULLCHAR) {
6884                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6885                 }
6886
6887                 cpstats.ok_to_send = 0;
6888                 cpstats.line_is_book = 0;
6889                 cpstats.nr_moves = 0;
6890                 cpstats.moves_left = 0;
6891
6892                 SendProgramStatsToFrontend( cps, &cpstats );
6893             }
6894         }
6895     }
6896 }
6897
6898
6899 /* Parse a game score from the character string "game", and
6900    record it as the history of the current game.  The game
6901    score is NOT assumed to start from the standard position. 
6902    The display is not updated in any way.
6903    */
6904 void
6905 ParseGameHistory(game)
6906      char *game;
6907 {
6908     ChessMove moveType;
6909     int fromX, fromY, toX, toY, boardIndex;
6910     char promoChar;
6911     char *p, *q;
6912     char buf[MSG_SIZ];
6913
6914     if (appData.debugMode)
6915       fprintf(debugFP, "Parsing game history: %s\n", game);
6916
6917     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6918     gameInfo.site = StrSave(appData.icsHost);
6919     gameInfo.date = PGNDate();
6920     gameInfo.round = StrSave("-");
6921
6922     /* Parse out names of players */
6923     while (*game == ' ') game++;
6924     p = buf;
6925     while (*game != ' ') *p++ = *game++;
6926     *p = NULLCHAR;
6927     gameInfo.white = StrSave(buf);
6928     while (*game == ' ') game++;
6929     p = buf;
6930     while (*game != ' ' && *game != '\n') *p++ = *game++;
6931     *p = NULLCHAR;
6932     gameInfo.black = StrSave(buf);
6933
6934     /* Parse moves */
6935     boardIndex = blackPlaysFirst ? 1 : 0;
6936     yynewstr(game);
6937     for (;;) {
6938         yyboardindex = boardIndex;
6939         moveType = (ChessMove) yylex();
6940         switch (moveType) {
6941           case IllegalMove:             /* maybe suicide chess, etc. */
6942   if (appData.debugMode) {
6943     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6944     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6945     setbuf(debugFP, NULL);
6946   }
6947           case WhitePromotionChancellor:
6948           case BlackPromotionChancellor:
6949           case WhitePromotionArchbishop:
6950           case BlackPromotionArchbishop:
6951           case WhitePromotionQueen:
6952           case BlackPromotionQueen:
6953           case WhitePromotionRook:
6954           case BlackPromotionRook:
6955           case WhitePromotionBishop:
6956           case BlackPromotionBishop:
6957           case WhitePromotionKnight:
6958           case BlackPromotionKnight:
6959           case WhitePromotionKing:
6960           case BlackPromotionKing:
6961           case NormalMove:
6962           case WhiteCapturesEnPassant:
6963           case BlackCapturesEnPassant:
6964           case WhiteKingSideCastle:
6965           case WhiteQueenSideCastle:
6966           case BlackKingSideCastle:
6967           case BlackQueenSideCastle:
6968           case WhiteKingSideCastleWild:
6969           case WhiteQueenSideCastleWild:
6970           case BlackKingSideCastleWild:
6971           case BlackQueenSideCastleWild:
6972           /* PUSH Fabien */
6973           case WhiteHSideCastleFR:
6974           case WhiteASideCastleFR:
6975           case BlackHSideCastleFR:
6976           case BlackASideCastleFR:
6977           /* POP Fabien */
6978             fromX = currentMoveString[0] - AAA;
6979             fromY = currentMoveString[1] - ONE;
6980             toX = currentMoveString[2] - AAA;
6981             toY = currentMoveString[3] - ONE;
6982             promoChar = currentMoveString[4];
6983             break;
6984           case WhiteDrop:
6985           case BlackDrop:
6986             fromX = moveType == WhiteDrop ?
6987               (int) CharToPiece(ToUpper(currentMoveString[0])) :
6988             (int) CharToPiece(ToLower(currentMoveString[0]));
6989             fromY = DROP_RANK;
6990             toX = currentMoveString[2] - AAA;
6991             toY = currentMoveString[3] - ONE;
6992             promoChar = NULLCHAR;
6993             break;
6994           case AmbiguousMove:
6995             /* bug? */
6996             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6997   if (appData.debugMode) {
6998     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6999     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7000     setbuf(debugFP, NULL);
7001   }
7002             DisplayError(buf, 0);
7003             return;
7004           case ImpossibleMove:
7005             /* bug? */
7006             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7007   if (appData.debugMode) {
7008     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7009     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7010     setbuf(debugFP, NULL);
7011   }
7012             DisplayError(buf, 0);
7013             return;
7014           case (ChessMove) 0:   /* end of file */
7015             if (boardIndex < backwardMostMove) {
7016                 /* Oops, gap.  How did that happen? */
7017                 DisplayError(_("Gap in move list"), 0);
7018                 return;
7019             }
7020             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7021             if (boardIndex > forwardMostMove) {
7022                 forwardMostMove = boardIndex;
7023             }
7024             return;
7025           case ElapsedTime:
7026             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7027                 strcat(parseList[boardIndex-1], " ");
7028                 strcat(parseList[boardIndex-1], yy_text);
7029             }
7030             continue;
7031           case Comment:
7032           case PGNTag:
7033           case NAG:
7034           default:
7035             /* ignore */
7036             continue;
7037           case WhiteWins:
7038           case BlackWins:
7039           case GameIsDrawn:
7040           case GameUnfinished:
7041             if (gameMode == IcsExamining) {
7042                 if (boardIndex < backwardMostMove) {
7043                     /* Oops, gap.  How did that happen? */
7044                     return;
7045                 }
7046                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7047                 return;
7048             }
7049             gameInfo.result = moveType;
7050             p = strchr(yy_text, '{');
7051             if (p == NULL) p = strchr(yy_text, '(');
7052             if (p == NULL) {
7053                 p = yy_text;
7054                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7055             } else {
7056                 q = strchr(p, *p == '{' ? '}' : ')');
7057                 if (q != NULL) *q = NULLCHAR;
7058                 p++;
7059             }
7060             gameInfo.resultDetails = StrSave(p);
7061             continue;
7062         }
7063         if (boardIndex >= forwardMostMove &&
7064             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7065             backwardMostMove = blackPlaysFirst ? 1 : 0;
7066             return;
7067         }
7068         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7069                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7070                                  parseList[boardIndex]);
7071         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7072         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7073         /* currentMoveString is set as a side-effect of yylex */
7074         strcpy(moveList[boardIndex], currentMoveString);
7075         strcat(moveList[boardIndex], "\n");
7076         boardIndex++;
7077         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7078                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7079         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7080                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7081           case MT_NONE:
7082           case MT_STALEMATE:
7083           default:
7084             break;
7085           case MT_CHECK:
7086             if(gameInfo.variant != VariantShogi)
7087                 strcat(parseList[boardIndex - 1], "+");
7088             break;
7089           case MT_CHECKMATE:
7090           case MT_STAINMATE:
7091             strcat(parseList[boardIndex - 1], "#");
7092             break;
7093         }
7094     }
7095 }
7096
7097
7098 /* Apply a move to the given board  */
7099 void
7100 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7101      int fromX, fromY, toX, toY;
7102      int promoChar;
7103      Board board;
7104      char *castling;
7105      char *ep;
7106 {
7107   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7108
7109     /* [HGM] compute & store e.p. status and castling rights for new position */
7110     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7111     { int i;
7112
7113       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7114       oldEP = *ep;
7115       *ep = EP_NONE;
7116
7117       if( board[toY][toX] != EmptySquare ) 
7118            *ep = EP_CAPTURE;  
7119
7120       if( board[fromY][fromX] == WhitePawn ) {
7121            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7122                *ep = EP_PAWN_MOVE;
7123            if( toY-fromY==2) {
7124                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7125                         gameInfo.variant != VariantBerolina || toX < fromX)
7126                       *ep = toX | berolina;
7127                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7128                         gameInfo.variant != VariantBerolina || toX > fromX) 
7129                       *ep = toX;
7130            }
7131       } else 
7132       if( board[fromY][fromX] == BlackPawn ) {
7133            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7134                *ep = EP_PAWN_MOVE; 
7135            if( toY-fromY== -2) {
7136                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7137                         gameInfo.variant != VariantBerolina || toX < fromX)
7138                       *ep = toX | berolina;
7139                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7140                         gameInfo.variant != VariantBerolina || toX > fromX) 
7141                       *ep = toX;
7142            }
7143        }
7144
7145        for(i=0; i<nrCastlingRights; i++) {
7146            if(castling[i] == fromX && castlingRank[i] == fromY ||
7147               castling[i] == toX   && castlingRank[i] == toY   
7148              ) castling[i] = -1; // revoke for moved or captured piece
7149        }
7150
7151     }
7152
7153   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7154   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7155        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7156          
7157   if (fromX == toX && fromY == toY) return;
7158
7159   if (fromY == DROP_RANK) {
7160         /* must be first */
7161         piece = board[toY][toX] = (ChessSquare) fromX;
7162   } else {
7163      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7164      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7165      if(gameInfo.variant == VariantKnightmate)
7166          king += (int) WhiteUnicorn - (int) WhiteKing;
7167
7168     /* Code added by Tord: */
7169     /* FRC castling assumed when king captures friendly rook. */
7170     if (board[fromY][fromX] == WhiteKing &&
7171              board[toY][toX] == WhiteRook) {
7172       board[fromY][fromX] = EmptySquare;
7173       board[toY][toX] = EmptySquare;
7174       if(toX > fromX) {
7175         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7176       } else {
7177         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7178       }
7179     } else if (board[fromY][fromX] == BlackKing &&
7180                board[toY][toX] == BlackRook) {
7181       board[fromY][fromX] = EmptySquare;
7182       board[toY][toX] = EmptySquare;
7183       if(toX > fromX) {
7184         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7185       } else {
7186         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7187       }
7188     /* End of code added by Tord */
7189
7190     } else if (board[fromY][fromX] == king
7191         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7192         && toY == fromY && toX > fromX+1) {
7193         board[fromY][fromX] = EmptySquare;
7194         board[toY][toX] = king;
7195         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7196         board[fromY][BOARD_RGHT-1] = EmptySquare;
7197     } else if (board[fromY][fromX] == king
7198         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7199                && toY == fromY && toX < fromX-1) {
7200         board[fromY][fromX] = EmptySquare;
7201         board[toY][toX] = king;
7202         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7203         board[fromY][BOARD_LEFT] = EmptySquare;
7204     } else if (board[fromY][fromX] == WhitePawn
7205                && toY == BOARD_HEIGHT-1
7206                && gameInfo.variant != VariantXiangqi
7207                ) {
7208         /* white pawn promotion */
7209         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7210         if (board[toY][toX] == EmptySquare) {
7211             board[toY][toX] = WhiteQueen;
7212         }
7213         if(gameInfo.variant==VariantBughouse ||
7214            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7215             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7216         board[fromY][fromX] = EmptySquare;
7217     } else if ((fromY == BOARD_HEIGHT-4)
7218                && (toX != fromX)
7219                && gameInfo.variant != VariantXiangqi
7220                && gameInfo.variant != VariantBerolina
7221                && (board[fromY][fromX] == WhitePawn)
7222                && (board[toY][toX] == EmptySquare)) {
7223         board[fromY][fromX] = EmptySquare;
7224         board[toY][toX] = WhitePawn;
7225         captured = board[toY - 1][toX];
7226         board[toY - 1][toX] = EmptySquare;
7227     } else if ((fromY == BOARD_HEIGHT-4)
7228                && (toX == fromX)
7229                && gameInfo.variant == VariantBerolina
7230                && (board[fromY][fromX] == WhitePawn)
7231                && (board[toY][toX] == EmptySquare)) {
7232         board[fromY][fromX] = EmptySquare;
7233         board[toY][toX] = WhitePawn;
7234         if(oldEP & EP_BEROLIN_A) {
7235                 captured = board[fromY][fromX-1];
7236                 board[fromY][fromX-1] = EmptySquare;
7237         }else{  captured = board[fromY][fromX+1];
7238                 board[fromY][fromX+1] = EmptySquare;
7239         }
7240     } else if (board[fromY][fromX] == king
7241         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7242                && toY == fromY && toX > fromX+1) {
7243         board[fromY][fromX] = EmptySquare;
7244         board[toY][toX] = king;
7245         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7246         board[fromY][BOARD_RGHT-1] = EmptySquare;
7247     } else if (board[fromY][fromX] == king
7248         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7249                && toY == fromY && toX < fromX-1) {
7250         board[fromY][fromX] = EmptySquare;
7251         board[toY][toX] = king;
7252         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7253         board[fromY][BOARD_LEFT] = EmptySquare;
7254     } else if (fromY == 7 && fromX == 3
7255                && board[fromY][fromX] == BlackKing
7256                && toY == 7 && toX == 5) {
7257         board[fromY][fromX] = EmptySquare;
7258         board[toY][toX] = BlackKing;
7259         board[fromY][7] = EmptySquare;
7260         board[toY][4] = BlackRook;
7261     } else if (fromY == 7 && fromX == 3
7262                && board[fromY][fromX] == BlackKing
7263                && toY == 7 && toX == 1) {
7264         board[fromY][fromX] = EmptySquare;
7265         board[toY][toX] = BlackKing;
7266         board[fromY][0] = EmptySquare;
7267         board[toY][2] = BlackRook;
7268     } else if (board[fromY][fromX] == BlackPawn
7269                && toY == 0
7270                && gameInfo.variant != VariantXiangqi
7271                ) {
7272         /* black pawn promotion */
7273         board[0][toX] = CharToPiece(ToLower(promoChar));
7274         if (board[0][toX] == EmptySquare) {
7275             board[0][toX] = BlackQueen;
7276         }
7277         if(gameInfo.variant==VariantBughouse ||
7278            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7279             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7280         board[fromY][fromX] = EmptySquare;
7281     } else if ((fromY == 3)
7282                && (toX != fromX)
7283                && gameInfo.variant != VariantXiangqi
7284                && gameInfo.variant != VariantBerolina
7285                && (board[fromY][fromX] == BlackPawn)
7286                && (board[toY][toX] == EmptySquare)) {
7287         board[fromY][fromX] = EmptySquare;
7288         board[toY][toX] = BlackPawn;
7289         captured = board[toY + 1][toX];
7290         board[toY + 1][toX] = EmptySquare;
7291     } else if ((fromY == 3)
7292                && (toX == fromX)
7293                && gameInfo.variant == VariantBerolina
7294                && (board[fromY][fromX] == BlackPawn)
7295                && (board[toY][toX] == EmptySquare)) {
7296         board[fromY][fromX] = EmptySquare;
7297         board[toY][toX] = BlackPawn;
7298         if(oldEP & EP_BEROLIN_A) {
7299                 captured = board[fromY][fromX-1];
7300                 board[fromY][fromX-1] = EmptySquare;
7301         }else{  captured = board[fromY][fromX+1];
7302                 board[fromY][fromX+1] = EmptySquare;
7303         }
7304     } else {
7305         board[toY][toX] = board[fromY][fromX];
7306         board[fromY][fromX] = EmptySquare;
7307     }
7308
7309     /* [HGM] now we promote for Shogi, if needed */
7310     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7311         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7312   }
7313
7314     if (gameInfo.holdingsWidth != 0) {
7315
7316       /* !!A lot more code needs to be written to support holdings  */
7317       /* [HGM] OK, so I have written it. Holdings are stored in the */
7318       /* penultimate board files, so they are automaticlly stored   */
7319       /* in the game history.                                       */
7320       if (fromY == DROP_RANK) {
7321         /* Delete from holdings, by decreasing count */
7322         /* and erasing image if necessary            */
7323         p = (int) fromX;
7324         if(p < (int) BlackPawn) { /* white drop */
7325              p -= (int)WhitePawn;
7326              if(p >= gameInfo.holdingsSize) p = 0;
7327              if(--board[p][BOARD_WIDTH-2] == 0)
7328                   board[p][BOARD_WIDTH-1] = EmptySquare;
7329         } else {                  /* black drop */
7330              p -= (int)BlackPawn;
7331              if(p >= gameInfo.holdingsSize) p = 0;
7332              if(--board[BOARD_HEIGHT-1-p][1] == 0)
7333                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7334         }
7335       }
7336       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7337           && gameInfo.variant != VariantBughouse        ) {
7338         /* [HGM] holdings: Add to holdings, if holdings exist */
7339         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7340                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7341                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7342         }
7343         p = (int) captured;
7344         if (p >= (int) BlackPawn) {
7345           p -= (int)BlackPawn;
7346           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7347                   /* in Shogi restore piece to its original  first */
7348                   captured = (ChessSquare) (DEMOTED captured);
7349                   p = DEMOTED p;
7350           }
7351           p = PieceToNumber((ChessSquare)p);
7352           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7353           board[p][BOARD_WIDTH-2]++;
7354           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7355         } else {
7356           p -= (int)WhitePawn;
7357           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7358                   captured = (ChessSquare) (DEMOTED captured);
7359                   p = DEMOTED p;
7360           }
7361           p = PieceToNumber((ChessSquare)p);
7362           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7363           board[BOARD_HEIGHT-1-p][1]++;
7364           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7365         }
7366       }
7367
7368     } else if (gameInfo.variant == VariantAtomic) {
7369       if (captured != EmptySquare) {
7370         int y, x;
7371         for (y = toY-1; y <= toY+1; y++) {
7372           for (x = toX-1; x <= toX+1; x++) {
7373             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7374                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7375               board[y][x] = EmptySquare;
7376             }
7377           }
7378         }
7379         board[toY][toX] = EmptySquare;
7380       }
7381     }
7382     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7383         /* [HGM] Shogi promotions */
7384         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7385     }
7386
7387     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7388                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7389         // [HGM] superchess: take promotion piece out of holdings
7390         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7391         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7392             if(!--board[k][BOARD_WIDTH-2])
7393                 board[k][BOARD_WIDTH-1] = EmptySquare;
7394         } else {
7395             if(!--board[BOARD_HEIGHT-1-k][1])
7396                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7397         }
7398     }
7399
7400 }
7401
7402 /* Updates forwardMostMove */
7403 void
7404 MakeMove(fromX, fromY, toX, toY, promoChar)
7405      int fromX, fromY, toX, toY;
7406      int promoChar;
7407 {
7408 //    forwardMostMove++; // [HGM] bare: moved downstream
7409
7410     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7411         int timeLeft; static int lastLoadFlag=0; int king, piece;
7412         piece = boards[forwardMostMove][fromY][fromX];
7413         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7414         if(gameInfo.variant == VariantKnightmate)
7415             king += (int) WhiteUnicorn - (int) WhiteKing;
7416         if(forwardMostMove == 0) {
7417             if(blackPlaysFirst) 
7418                 fprintf(serverMoves, "%s;", second.tidy);
7419             fprintf(serverMoves, "%s;", first.tidy);
7420             if(!blackPlaysFirst) 
7421                 fprintf(serverMoves, "%s;", second.tidy);
7422         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7423         lastLoadFlag = loadFlag;
7424         // print base move
7425         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7426         // print castling suffix
7427         if( toY == fromY && piece == king ) {
7428             if(toX-fromX > 1)
7429                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7430             if(fromX-toX >1)
7431                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7432         }
7433         // e.p. suffix
7434         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7435              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7436              boards[forwardMostMove][toY][toX] == EmptySquare
7437              && fromX != toX )
7438                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7439         // promotion suffix
7440         if(promoChar != NULLCHAR)
7441                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7442         if(!loadFlag) {
7443             fprintf(serverMoves, "/%d/%d",
7444                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7445             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7446             else                      timeLeft = blackTimeRemaining/1000;
7447             fprintf(serverMoves, "/%d", timeLeft);
7448         }
7449         fflush(serverMoves);
7450     }
7451
7452     if (forwardMostMove+1 >= MAX_MOVES) {
7453       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7454                         0, 1);
7455       return;
7456     }
7457     SwitchClocks();
7458     timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
7459     timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
7460     if (commentList[forwardMostMove+1] != NULL) {
7461         free(commentList[forwardMostMove+1]);
7462         commentList[forwardMostMove+1] = NULL;
7463     }
7464     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7465     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7466     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7467                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7468     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7469     gameInfo.result = GameUnfinished;
7470     if (gameInfo.resultDetails != NULL) {
7471         free(gameInfo.resultDetails);
7472         gameInfo.resultDetails = NULL;
7473     }
7474     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7475                               moveList[forwardMostMove - 1]);
7476     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7477                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7478                              fromY, fromX, toY, toX, promoChar,
7479                              parseList[forwardMostMove - 1]);
7480     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7481                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7482                             castlingRights[forwardMostMove]) ) {
7483       case MT_NONE:
7484       case MT_STALEMATE:
7485       default:
7486         break;
7487       case MT_CHECK:
7488         if(gameInfo.variant != VariantShogi)
7489             strcat(parseList[forwardMostMove - 1], "+");
7490         break;
7491       case MT_CHECKMATE:
7492       case MT_STAINMATE:
7493         strcat(parseList[forwardMostMove - 1], "#");
7494         break;
7495     }
7496     if (appData.debugMode) {
7497         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7498     }
7499
7500 }
7501
7502 /* Updates currentMove if not pausing */
7503 void
7504 ShowMove(fromX, fromY, toX, toY)
7505 {
7506     int instant = (gameMode == PlayFromGameFile) ?
7507         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7508     if(appData.noGUI) return;
7509     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7510         if (!instant) {
7511             if (forwardMostMove == currentMove + 1) {
7512                 AnimateMove(boards[forwardMostMove - 1],
7513                             fromX, fromY, toX, toY);
7514             }
7515             if (appData.highlightLastMove) {
7516                 SetHighlights(fromX, fromY, toX, toY);
7517             }
7518         }
7519         currentMove = forwardMostMove;
7520     }
7521
7522     if (instant) return;
7523
7524     DisplayMove(currentMove - 1);
7525     DrawPosition(FALSE, boards[currentMove]);
7526     DisplayBothClocks();
7527     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7528 }
7529
7530 void SendEgtPath(ChessProgramState *cps)
7531 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7532         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7533
7534         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7535
7536         while(*p) {
7537             char c, *q = name+1, *r, *s;
7538
7539             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7540             while(*p && *p != ',') *q++ = *p++;
7541             *q++ = ':'; *q = 0;
7542             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7543                 strcmp(name, ",nalimov:") == 0 ) {
7544                 // take nalimov path from the menu-changeable option first, if it is defined
7545                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7546                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7547             } else
7548             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7549                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7550                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7551                 s = r = StrStr(s, ":") + 1; // beginning of path info
7552                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7553                 c = *r; *r = 0;             // temporarily null-terminate path info
7554                     *--q = 0;               // strip of trailig ':' from name
7555                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7556                 *r = c;
7557                 SendToProgram(buf,cps);     // send egtbpath command for this format
7558             }
7559             if(*p == ',') p++; // read away comma to position for next format name
7560         }
7561 }
7562
7563 void
7564 InitChessProgram(cps, setup)
7565      ChessProgramState *cps;
7566      int setup; /* [HGM] needed to setup FRC opening position */
7567 {
7568     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7569     if (appData.noChessProgram) return;
7570     hintRequested = FALSE;
7571     bookRequested = FALSE;
7572
7573     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7574     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7575     if(cps->memSize) { /* [HGM] memory */
7576         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7577         SendToProgram(buf, cps);
7578     }
7579     SendEgtPath(cps); /* [HGM] EGT */
7580     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7581         sprintf(buf, "cores %d\n", appData.smpCores);
7582         SendToProgram(buf, cps);
7583     }
7584
7585     SendToProgram(cps->initString, cps);
7586     if (gameInfo.variant != VariantNormal &&
7587         gameInfo.variant != VariantLoadable
7588         /* [HGM] also send variant if board size non-standard */
7589         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7590                                             ) {
7591       char *v = VariantName(gameInfo.variant);
7592       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7593         /* [HGM] in protocol 1 we have to assume all variants valid */
7594         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7595         DisplayFatalError(buf, 0, 1);
7596         return;
7597       }
7598
7599       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7600       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7601       if( gameInfo.variant == VariantXiangqi )
7602            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7603       if( gameInfo.variant == VariantShogi )
7604            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7605       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7606            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7607       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7608                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7609            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7610       if( gameInfo.variant == VariantCourier )
7611            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7612       if( gameInfo.variant == VariantSuper )
7613            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7614       if( gameInfo.variant == VariantGreat )
7615            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7616
7617       if(overruled) {
7618            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7619                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7620            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7621            if(StrStr(cps->variants, b) == NULL) { 
7622                // specific sized variant not known, check if general sizing allowed
7623                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7624                    if(StrStr(cps->variants, "boardsize") == NULL) {
7625                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7626                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7627                        DisplayFatalError(buf, 0, 1);
7628                        return;
7629                    }
7630                    /* [HGM] here we really should compare with the maximum supported board size */
7631                }
7632            }
7633       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7634       sprintf(buf, "variant %s\n", b);
7635       SendToProgram(buf, cps);
7636     }
7637     currentlyInitializedVariant = gameInfo.variant;
7638
7639     /* [HGM] send opening position in FRC to first engine */
7640     if(setup) {
7641           SendToProgram("force\n", cps);
7642           SendBoard(cps, 0);
7643           /* engine is now in force mode! Set flag to wake it up after first move. */
7644           setboardSpoiledMachineBlack = 1;
7645     }
7646
7647     if (cps->sendICS) {
7648       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7649       SendToProgram(buf, cps);
7650     }
7651     cps->maybeThinking = FALSE;
7652     cps->offeredDraw = 0;
7653     if (!appData.icsActive) {
7654         SendTimeControl(cps, movesPerSession, timeControl,
7655                         timeIncrement, appData.searchDepth,
7656                         searchTime);
7657     }
7658     if (appData.showThinking 
7659         // [HGM] thinking: four options require thinking output to be sent
7660         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7661                                 ) {
7662         SendToProgram("post\n", cps);
7663     }
7664     SendToProgram("hard\n", cps);
7665     if (!appData.ponderNextMove) {
7666         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7667            it without being sure what state we are in first.  "hard"
7668            is not a toggle, so that one is OK.
7669          */
7670         SendToProgram("easy\n", cps);
7671     }
7672     if (cps->usePing) {
7673       sprintf(buf, "ping %d\n", ++cps->lastPing);
7674       SendToProgram(buf, cps);
7675     }
7676     cps->initDone = TRUE;
7677 }   
7678
7679
7680 void
7681 StartChessProgram(cps)
7682      ChessProgramState *cps;
7683 {
7684     char buf[MSG_SIZ];
7685     int err;
7686
7687     if (appData.noChessProgram) return;
7688     cps->initDone = FALSE;
7689
7690     if (strcmp(cps->host, "localhost") == 0) {
7691         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7692     } else if (*appData.remoteShell == NULLCHAR) {
7693         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7694     } else {
7695         if (*appData.remoteUser == NULLCHAR) {
7696           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7697                     cps->program);
7698         } else {
7699           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7700                     cps->host, appData.remoteUser, cps->program);
7701         }
7702         err = StartChildProcess(buf, "", &cps->pr);
7703     }
7704     
7705     if (err != 0) {
7706         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7707         DisplayFatalError(buf, err, 1);
7708         cps->pr = NoProc;
7709         cps->isr = NULL;
7710         return;
7711     }
7712     
7713     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7714     if (cps->protocolVersion > 1) {
7715       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7716       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7717       cps->comboCnt = 0;  //                and values of combo boxes
7718       SendToProgram(buf, cps);
7719     } else {
7720       SendToProgram("xboard\n", cps);
7721     }
7722 }
7723
7724
7725 void
7726 TwoMachinesEventIfReady P((void))
7727 {
7728   if (first.lastPing != first.lastPong) {
7729     DisplayMessage("", _("Waiting for first chess program"));
7730     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7731     return;
7732   }
7733   if (second.lastPing != second.lastPong) {
7734     DisplayMessage("", _("Waiting for second chess program"));
7735     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7736     return;
7737   }
7738   ThawUI();
7739   TwoMachinesEvent();
7740 }
7741
7742 void
7743 NextMatchGame P((void))
7744 {
7745     int index; /* [HGM] autoinc: step lod index during match */
7746     Reset(FALSE, TRUE);
7747     if (*appData.loadGameFile != NULLCHAR) {
7748         index = appData.loadGameIndex;
7749         if(index < 0) { // [HGM] autoinc
7750             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7751             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7752         } 
7753         LoadGameFromFile(appData.loadGameFile,
7754                          index,
7755                          appData.loadGameFile, FALSE);
7756     } else if (*appData.loadPositionFile != NULLCHAR) {
7757         index = appData.loadPositionIndex;
7758         if(index < 0) { // [HGM] autoinc
7759             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7760             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7761         } 
7762         LoadPositionFromFile(appData.loadPositionFile,
7763                              index,
7764                              appData.loadPositionFile);
7765     }
7766     TwoMachinesEventIfReady();
7767 }
7768
7769 void UserAdjudicationEvent( int result )
7770 {
7771     ChessMove gameResult = GameIsDrawn;
7772
7773     if( result > 0 ) {
7774         gameResult = WhiteWins;
7775     }
7776     else if( result < 0 ) {
7777         gameResult = BlackWins;
7778     }
7779
7780     if( gameMode == TwoMachinesPlay ) {
7781         GameEnds( gameResult, "User adjudication", GE_XBOARD );
7782     }
7783 }
7784
7785
7786 // [HGM] save: calculate checksum of game to make games easily identifiable
7787 int StringCheckSum(char *s)
7788 {
7789         int i = 0;
7790         if(s==NULL) return 0;
7791         while(*s) i = i*259 + *s++;
7792         return i;
7793 }
7794
7795 int GameCheckSum()
7796 {
7797         int i, sum=0;
7798         for(i=backwardMostMove; i<forwardMostMove; i++) {
7799                 sum += pvInfoList[i].depth;
7800                 sum += StringCheckSum(parseList[i]);
7801                 sum += StringCheckSum(commentList[i]);
7802                 sum *= 261;
7803         }
7804         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7805         return sum + StringCheckSum(commentList[i]);
7806 } // end of save patch
7807
7808 void
7809 GameEnds(result, resultDetails, whosays)
7810      ChessMove result;
7811      char *resultDetails;
7812      int whosays;
7813 {
7814     GameMode nextGameMode;
7815     int isIcsGame;
7816     char buf[MSG_SIZ];
7817
7818     if(endingGame) return; /* [HGM] crash: forbid recursion */
7819     endingGame = 1;
7820
7821     if (appData.debugMode) {
7822       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7823               result, resultDetails ? resultDetails : "(null)", whosays);
7824     }
7825
7826     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7827         /* If we are playing on ICS, the server decides when the
7828            game is over, but the engine can offer to draw, claim 
7829            a draw, or resign. 
7830          */
7831 #if ZIPPY
7832         if (appData.zippyPlay && first.initDone) {
7833             if (result == GameIsDrawn) {
7834                 /* In case draw still needs to be claimed */
7835                 SendToICS(ics_prefix);
7836                 SendToICS("draw\n");
7837             } else if (StrCaseStr(resultDetails, "resign")) {
7838                 SendToICS(ics_prefix);
7839                 SendToICS("resign\n");
7840             }
7841         }
7842 #endif
7843         endingGame = 0; /* [HGM] crash */
7844         return;
7845     }
7846
7847     /* If we're loading the game from a file, stop */
7848     if (whosays == GE_FILE) {
7849       (void) StopLoadGameTimer();
7850       gameFileFP = NULL;
7851     }
7852
7853     /* Cancel draw offers */
7854     first.offeredDraw = second.offeredDraw = 0;
7855
7856     /* If this is an ICS game, only ICS can really say it's done;
7857        if not, anyone can. */
7858     isIcsGame = (gameMode == IcsPlayingWhite || 
7859                  gameMode == IcsPlayingBlack || 
7860                  gameMode == IcsObserving    || 
7861                  gameMode == IcsExamining);
7862
7863     if (!isIcsGame || whosays == GE_ICS) {
7864         /* OK -- not an ICS game, or ICS said it was done */
7865         StopClocks();
7866         if (!isIcsGame && !appData.noChessProgram) 
7867           SetUserThinkingEnables();
7868     
7869         /* [HGM] if a machine claims the game end we verify this claim */
7870         if(gameMode == TwoMachinesPlay && appData.testClaims) {
7871             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7872                 char claimer;
7873                 ChessMove trueResult = (ChessMove) -1;
7874
7875                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
7876                                             first.twoMachinesColor[0] :
7877                                             second.twoMachinesColor[0] ;
7878
7879                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7880                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7881                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7882                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7883                 } else
7884                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7885                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7886                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7887                 } else
7888                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7889                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7890                 }
7891
7892                 // now verify win claims, but not in drop games, as we don't understand those yet
7893                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7894                                                  || gameInfo.variant == VariantGreat) &&
7895                     (result == WhiteWins && claimer == 'w' ||
7896                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
7897                       if (appData.debugMode) {
7898                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
7899                                 result, epStatus[forwardMostMove], forwardMostMove);
7900                       }
7901                       if(result != trueResult) {
7902                               sprintf(buf, "False win claim: '%s'", resultDetails);
7903                               result = claimer == 'w' ? BlackWins : WhiteWins;
7904                               resultDetails = buf;
7905                       }
7906                 } else
7907                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7908                     && (forwardMostMove <= backwardMostMove ||
7909                         epStatus[forwardMostMove-1] > EP_DRAWS ||
7910                         (claimer=='b')==(forwardMostMove&1))
7911                                                                                   ) {
7912                       /* [HGM] verify: draws that were not flagged are false claims */
7913                       sprintf(buf, "False draw claim: '%s'", resultDetails);
7914                       result = claimer == 'w' ? BlackWins : WhiteWins;
7915                       resultDetails = buf;
7916                 }
7917                 /* (Claiming a loss is accepted no questions asked!) */
7918             }
7919             /* [HGM] bare: don't allow bare King to win */
7920             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7921                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
7922                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7923                && result != GameIsDrawn)
7924             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7925                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7926                         int p = (int)boards[forwardMostMove][i][j] - color;
7927                         if(p >= 0 && p <= (int)WhiteKing) k++;
7928                 }
7929                 if (appData.debugMode) {
7930                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7931                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7932                 }
7933                 if(k <= 1) {
7934                         result = GameIsDrawn;
7935                         sprintf(buf, "%s but bare king", resultDetails);
7936                         resultDetails = buf;
7937                 }
7938             }
7939         }
7940
7941
7942         if(serverMoves != NULL && !loadFlag) { char c = '=';
7943             if(result==WhiteWins) c = '+';
7944             if(result==BlackWins) c = '-';
7945             if(resultDetails != NULL)
7946                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7947         }
7948         if (resultDetails != NULL) {
7949             gameInfo.result = result;
7950             gameInfo.resultDetails = StrSave(resultDetails);
7951
7952             /* display last move only if game was not loaded from file */
7953             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7954                 DisplayMove(currentMove - 1);
7955     
7956             if (forwardMostMove != 0) {
7957                 if (gameMode != PlayFromGameFile && gameMode != EditGame
7958                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7959                                                                 ) {
7960                     if (*appData.saveGameFile != NULLCHAR) {
7961                         SaveGameToFile(appData.saveGameFile, TRUE);
7962                     } else if (appData.autoSaveGames) {
7963                         AutoSaveGame();
7964                     }
7965                     if (*appData.savePositionFile != NULLCHAR) {
7966                         SavePositionToFile(appData.savePositionFile);
7967                     }
7968                 }
7969             }
7970
7971             /* Tell program how game ended in case it is learning */
7972             /* [HGM] Moved this to after saving the PGN, just in case */
7973             /* engine died and we got here through time loss. In that */
7974             /* case we will get a fatal error writing the pipe, which */
7975             /* would otherwise lose us the PGN.                       */
7976             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
7977             /* output during GameEnds should never be fatal anymore   */
7978             if (gameMode == MachinePlaysWhite ||
7979                 gameMode == MachinePlaysBlack ||
7980                 gameMode == TwoMachinesPlay ||
7981                 gameMode == IcsPlayingWhite ||
7982                 gameMode == IcsPlayingBlack ||
7983                 gameMode == BeginningOfGame) {
7984                 char buf[MSG_SIZ];
7985                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7986                         resultDetails);
7987                 if (first.pr != NoProc) {
7988                     SendToProgram(buf, &first);
7989                 }
7990                 if (second.pr != NoProc &&
7991                     gameMode == TwoMachinesPlay) {
7992                     SendToProgram(buf, &second);
7993                 }
7994             }
7995         }
7996
7997         if (appData.icsActive) {
7998             if (appData.quietPlay &&
7999                 (gameMode == IcsPlayingWhite ||
8000                  gameMode == IcsPlayingBlack)) {
8001                 SendToICS(ics_prefix);
8002                 SendToICS("set shout 1\n");
8003             }
8004             nextGameMode = IcsIdle;
8005             ics_user_moved = FALSE;
8006             /* clean up premove.  It's ugly when the game has ended and the
8007              * premove highlights are still on the board.
8008              */
8009             if (gotPremove) {
8010               gotPremove = FALSE;
8011               ClearPremoveHighlights();
8012               DrawPosition(FALSE, boards[currentMove]);
8013             }
8014             if (whosays == GE_ICS) {
8015                 switch (result) {
8016                 case WhiteWins:
8017                     if (gameMode == IcsPlayingWhite)
8018                         PlayIcsWinSound();
8019                     else if(gameMode == IcsPlayingBlack)
8020                         PlayIcsLossSound();
8021                     break;
8022                 case BlackWins:
8023                     if (gameMode == IcsPlayingBlack)
8024                         PlayIcsWinSound();
8025                     else if(gameMode == IcsPlayingWhite)
8026                         PlayIcsLossSound();
8027                     break;
8028                 case GameIsDrawn:
8029                     PlayIcsDrawSound();
8030                     break;
8031                 default:
8032                     PlayIcsUnfinishedSound();
8033                 }
8034             }
8035         } else if (gameMode == EditGame ||
8036                    gameMode == PlayFromGameFile || 
8037                    gameMode == AnalyzeMode || 
8038                    gameMode == AnalyzeFile) {
8039             nextGameMode = gameMode;
8040         } else {
8041             nextGameMode = EndOfGame;
8042         }
8043         pausing = FALSE;
8044         ModeHighlight();
8045     } else {
8046         nextGameMode = gameMode;
8047     }
8048
8049     if (appData.noChessProgram) {
8050         gameMode = nextGameMode;
8051         ModeHighlight();
8052         endingGame = 0; /* [HGM] crash */
8053         return;
8054     }
8055
8056     if (first.reuse) {
8057         /* Put first chess program into idle state */
8058         if (first.pr != NoProc &&
8059             (gameMode == MachinePlaysWhite ||
8060              gameMode == MachinePlaysBlack ||
8061              gameMode == TwoMachinesPlay ||
8062              gameMode == IcsPlayingWhite ||
8063              gameMode == IcsPlayingBlack ||
8064              gameMode == BeginningOfGame)) {
8065             SendToProgram("force\n", &first);
8066             if (first.usePing) {
8067               char buf[MSG_SIZ];
8068               sprintf(buf, "ping %d\n", ++first.lastPing);
8069               SendToProgram(buf, &first);
8070             }
8071         }
8072     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8073         /* Kill off first chess program */
8074         if (first.isr != NULL)
8075           RemoveInputSource(first.isr);
8076         first.isr = NULL;
8077     
8078         if (first.pr != NoProc) {
8079             ExitAnalyzeMode();
8080             DoSleep( appData.delayBeforeQuit );
8081             SendToProgram("quit\n", &first);
8082             DoSleep( appData.delayAfterQuit );
8083             DestroyChildProcess(first.pr, first.useSigterm);
8084         }
8085         first.pr = NoProc;
8086     }
8087     if (second.reuse) {
8088         /* Put second chess program into idle state */
8089         if (second.pr != NoProc &&
8090             gameMode == TwoMachinesPlay) {
8091             SendToProgram("force\n", &second);
8092             if (second.usePing) {
8093               char buf[MSG_SIZ];
8094               sprintf(buf, "ping %d\n", ++second.lastPing);
8095               SendToProgram(buf, &second);
8096             }
8097         }
8098     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8099         /* Kill off second chess program */
8100         if (second.isr != NULL)
8101           RemoveInputSource(second.isr);
8102         second.isr = NULL;
8103     
8104         if (second.pr != NoProc) {
8105             DoSleep( appData.delayBeforeQuit );
8106             SendToProgram("quit\n", &second);
8107             DoSleep( appData.delayAfterQuit );
8108             DestroyChildProcess(second.pr, second.useSigterm);
8109         }
8110         second.pr = NoProc;
8111     }
8112
8113     if (matchMode && gameMode == TwoMachinesPlay) {
8114         switch (result) {
8115         case WhiteWins:
8116           if (first.twoMachinesColor[0] == 'w') {
8117             first.matchWins++;
8118           } else {
8119             second.matchWins++;
8120           }
8121           break;
8122         case BlackWins:
8123           if (first.twoMachinesColor[0] == 'b') {
8124             first.matchWins++;
8125           } else {
8126             second.matchWins++;
8127           }
8128           break;
8129         default:
8130           break;
8131         }
8132         if (matchGame < appData.matchGames) {
8133             char *tmp;
8134             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8135                 tmp = first.twoMachinesColor;
8136                 first.twoMachinesColor = second.twoMachinesColor;
8137                 second.twoMachinesColor = tmp;
8138             }
8139             gameMode = nextGameMode;
8140             matchGame++;
8141             if(appData.matchPause>10000 || appData.matchPause<10)
8142                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8143             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8144             endingGame = 0; /* [HGM] crash */
8145             return;
8146         } else {
8147             char buf[MSG_SIZ];
8148             gameMode = nextGameMode;
8149             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8150                     first.tidy, second.tidy,
8151                     first.matchWins, second.matchWins,
8152                     appData.matchGames - (first.matchWins + second.matchWins));
8153             DisplayFatalError(buf, 0, 0);
8154         }
8155     }
8156     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8157         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8158       ExitAnalyzeMode();
8159     gameMode = nextGameMode;
8160     ModeHighlight();
8161     endingGame = 0;  /* [HGM] crash */
8162 }
8163
8164 /* Assumes program was just initialized (initString sent).
8165    Leaves program in force mode. */
8166 void
8167 FeedMovesToProgram(cps, upto) 
8168      ChessProgramState *cps;
8169      int upto;
8170 {
8171     int i;
8172     
8173     if (appData.debugMode)
8174       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8175               startedFromSetupPosition ? "position and " : "",
8176               backwardMostMove, upto, cps->which);
8177     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8178         // [HGM] variantswitch: make engine aware of new variant
8179         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8180                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8181         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8182         SendToProgram(buf, cps);
8183         currentlyInitializedVariant = gameInfo.variant;
8184     }
8185     SendToProgram("force\n", cps);
8186     if (startedFromSetupPosition) {
8187         SendBoard(cps, backwardMostMove);
8188     if (appData.debugMode) {
8189         fprintf(debugFP, "feedMoves\n");
8190     }
8191     }
8192     for (i = backwardMostMove; i < upto; i++) {
8193         SendMoveToProgram(i, cps);
8194     }
8195 }
8196
8197
8198 void
8199 ResurrectChessProgram()
8200 {
8201      /* The chess program may have exited.
8202         If so, restart it and feed it all the moves made so far. */
8203
8204     if (appData.noChessProgram || first.pr != NoProc) return;
8205     
8206     StartChessProgram(&first);
8207     InitChessProgram(&first, FALSE);
8208     FeedMovesToProgram(&first, currentMove);
8209
8210     if (!first.sendTime) {
8211         /* can't tell gnuchess what its clock should read,
8212            so we bow to its notion. */
8213         ResetClocks();
8214         timeRemaining[0][currentMove] = whiteTimeRemaining;
8215         timeRemaining[1][currentMove] = blackTimeRemaining;
8216     }
8217
8218     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8219                 appData.icsEngineAnalyze) && first.analysisSupport) {
8220       SendToProgram("analyze\n", &first);
8221       first.analyzing = TRUE;
8222     }
8223 }
8224
8225 /*
8226  * Button procedures
8227  */
8228 void
8229 Reset(redraw, init)
8230      int redraw, init;
8231 {
8232     int i;
8233
8234     if (appData.debugMode) {
8235         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8236                 redraw, init, gameMode);
8237     }
8238     pausing = pauseExamInvalid = FALSE;
8239     startedFromSetupPosition = blackPlaysFirst = FALSE;
8240     firstMove = TRUE;
8241     whiteFlag = blackFlag = FALSE;
8242     userOfferedDraw = FALSE;
8243     hintRequested = bookRequested = FALSE;
8244     first.maybeThinking = FALSE;
8245     second.maybeThinking = FALSE;
8246     first.bookSuspend = FALSE; // [HGM] book
8247     second.bookSuspend = FALSE;
8248     thinkOutput[0] = NULLCHAR;
8249     lastHint[0] = NULLCHAR;
8250     ClearGameInfo(&gameInfo);
8251     gameInfo.variant = StringToVariant(appData.variant);
8252     ics_user_moved = ics_clock_paused = FALSE;
8253     ics_getting_history = H_FALSE;
8254     ics_gamenum = -1;
8255     white_holding[0] = black_holding[0] = NULLCHAR;
8256     ClearProgramStats();
8257     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8258     
8259     ResetFrontEnd();
8260     ClearHighlights();
8261     flipView = appData.flipView;
8262     ClearPremoveHighlights();
8263     gotPremove = FALSE;
8264     alarmSounded = FALSE;
8265
8266     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8267     if(appData.serverMovesName != NULL) {
8268         /* [HGM] prepare to make moves file for broadcasting */
8269         clock_t t = clock();
8270         if(serverMoves != NULL) fclose(serverMoves);
8271         serverMoves = fopen(appData.serverMovesName, "r");
8272         if(serverMoves != NULL) {
8273             fclose(serverMoves);
8274             /* delay 15 sec before overwriting, so all clients can see end */
8275             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8276         }
8277         serverMoves = fopen(appData.serverMovesName, "w");
8278     }
8279
8280     ExitAnalyzeMode();
8281     gameMode = BeginningOfGame;
8282     ModeHighlight();
8283     if(appData.icsActive) gameInfo.variant = VariantNormal;
8284     InitPosition(redraw);
8285     for (i = 0; i < MAX_MOVES; i++) {
8286         if (commentList[i] != NULL) {
8287             free(commentList[i]);
8288             commentList[i] = NULL;
8289         }
8290     }
8291     ResetClocks();
8292     timeRemaining[0][0] = whiteTimeRemaining;
8293     timeRemaining[1][0] = blackTimeRemaining;
8294     if (first.pr == NULL) {
8295         StartChessProgram(&first);
8296     }
8297     if (init) {
8298             InitChessProgram(&first, startedFromSetupPosition);
8299     }
8300     DisplayTitle("");
8301     DisplayMessage("", "");
8302     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8303     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8304 }
8305
8306 void
8307 AutoPlayGameLoop()
8308 {
8309     for (;;) {
8310         if (!AutoPlayOneMove())
8311           return;
8312         if (matchMode || appData.timeDelay == 0)
8313           continue;
8314         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8315           return;
8316         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8317         break;
8318     }
8319 }
8320
8321
8322 int
8323 AutoPlayOneMove()
8324 {
8325     int fromX, fromY, toX, toY;
8326
8327     if (appData.debugMode) {
8328       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8329     }
8330
8331     if (gameMode != PlayFromGameFile)
8332       return FALSE;
8333
8334     if (currentMove >= forwardMostMove) {
8335       gameMode = EditGame;
8336       ModeHighlight();
8337
8338       /* [AS] Clear current move marker at the end of a game */
8339       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8340
8341       return FALSE;
8342     }
8343     
8344     toX = moveList[currentMove][2] - AAA;
8345     toY = moveList[currentMove][3] - ONE;
8346
8347     if (moveList[currentMove][1] == '@') {
8348         if (appData.highlightLastMove) {
8349             SetHighlights(-1, -1, toX, toY);
8350         }
8351     } else {
8352         fromX = moveList[currentMove][0] - AAA;
8353         fromY = moveList[currentMove][1] - ONE;
8354
8355         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8356
8357         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8358
8359         if (appData.highlightLastMove) {
8360             SetHighlights(fromX, fromY, toX, toY);
8361         }
8362     }
8363     DisplayMove(currentMove);
8364     SendMoveToProgram(currentMove++, &first);
8365     DisplayBothClocks();
8366     DrawPosition(FALSE, boards[currentMove]);
8367     // [HGM] PV info: always display, routine tests if empty
8368     DisplayComment(currentMove - 1, commentList[currentMove]);
8369     return TRUE;
8370 }
8371
8372
8373 int
8374 LoadGameOneMove(readAhead)
8375      ChessMove readAhead;
8376 {
8377     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8378     char promoChar = NULLCHAR;
8379     ChessMove moveType;
8380     char move[MSG_SIZ];
8381     char *p, *q;
8382     
8383     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8384         gameMode != AnalyzeMode && gameMode != Training) {
8385         gameFileFP = NULL;
8386         return FALSE;
8387     }
8388     
8389     yyboardindex = forwardMostMove;
8390     if (readAhead != (ChessMove)0) {
8391       moveType = readAhead;
8392     } else {
8393       if (gameFileFP == NULL)
8394           return FALSE;
8395       moveType = (ChessMove) yylex();
8396     }
8397     
8398     done = FALSE;
8399     switch (moveType) {
8400       case Comment:
8401         if (appData.debugMode) 
8402           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8403         p = yy_text;
8404         if (*p == '{' || *p == '[' || *p == '(') {
8405             p[strlen(p) - 1] = NULLCHAR;
8406             p++;
8407         }
8408
8409         /* append the comment but don't display it */
8410         while (*p == '\n') p++;
8411         AppendComment(currentMove, p);
8412         return TRUE;
8413
8414       case WhiteCapturesEnPassant:
8415       case BlackCapturesEnPassant:
8416       case WhitePromotionChancellor:
8417       case BlackPromotionChancellor:
8418       case WhitePromotionArchbishop:
8419       case BlackPromotionArchbishop:
8420       case WhitePromotionCentaur:
8421       case BlackPromotionCentaur:
8422       case WhitePromotionQueen:
8423       case BlackPromotionQueen:
8424       case WhitePromotionRook:
8425       case BlackPromotionRook:
8426       case WhitePromotionBishop:
8427       case BlackPromotionBishop:
8428       case WhitePromotionKnight:
8429       case BlackPromotionKnight:
8430       case WhitePromotionKing:
8431       case BlackPromotionKing:
8432       case NormalMove:
8433       case WhiteKingSideCastle:
8434       case WhiteQueenSideCastle:
8435       case BlackKingSideCastle:
8436       case BlackQueenSideCastle:
8437       case WhiteKingSideCastleWild:
8438       case WhiteQueenSideCastleWild:
8439       case BlackKingSideCastleWild:
8440       case BlackQueenSideCastleWild:
8441       /* PUSH Fabien */
8442       case WhiteHSideCastleFR:
8443       case WhiteASideCastleFR:
8444       case BlackHSideCastleFR:
8445       case BlackASideCastleFR:
8446       /* POP Fabien */
8447         if (appData.debugMode)
8448           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8449         fromX = currentMoveString[0] - AAA;
8450         fromY = currentMoveString[1] - ONE;
8451         toX = currentMoveString[2] - AAA;
8452         toY = currentMoveString[3] - ONE;
8453         promoChar = currentMoveString[4];
8454         break;
8455
8456       case WhiteDrop:
8457       case BlackDrop:
8458         if (appData.debugMode)
8459           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8460         fromX = moveType == WhiteDrop ?
8461           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8462         (int) CharToPiece(ToLower(currentMoveString[0]));
8463         fromY = DROP_RANK;
8464         toX = currentMoveString[2] - AAA;
8465         toY = currentMoveString[3] - ONE;
8466         break;
8467
8468       case WhiteWins:
8469       case BlackWins:
8470       case GameIsDrawn:
8471       case GameUnfinished:
8472         if (appData.debugMode)
8473           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8474         p = strchr(yy_text, '{');
8475         if (p == NULL) p = strchr(yy_text, '(');
8476         if (p == NULL) {
8477             p = yy_text;
8478             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8479         } else {
8480             q = strchr(p, *p == '{' ? '}' : ')');
8481             if (q != NULL) *q = NULLCHAR;
8482             p++;
8483         }
8484         GameEnds(moveType, p, GE_FILE);
8485         done = TRUE;
8486         if (cmailMsgLoaded) {
8487             ClearHighlights();
8488             flipView = WhiteOnMove(currentMove);
8489             if (moveType == GameUnfinished) flipView = !flipView;
8490             if (appData.debugMode)
8491               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8492         }
8493         break;
8494
8495       case (ChessMove) 0:       /* end of file */
8496         if (appData.debugMode)
8497           fprintf(debugFP, "Parser hit end of file\n");
8498         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8499                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8500           case MT_NONE:
8501           case MT_CHECK:
8502             break;
8503           case MT_CHECKMATE:
8504           case MT_STAINMATE:
8505             if (WhiteOnMove(currentMove)) {
8506                 GameEnds(BlackWins, "Black mates", GE_FILE);
8507             } else {
8508                 GameEnds(WhiteWins, "White mates", GE_FILE);
8509             }
8510             break;
8511           case MT_STALEMATE:
8512             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8513             break;
8514         }
8515         done = TRUE;
8516         break;
8517
8518       case MoveNumberOne:
8519         if (lastLoadGameStart == GNUChessGame) {
8520             /* GNUChessGames have numbers, but they aren't move numbers */
8521             if (appData.debugMode)
8522               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8523                       yy_text, (int) moveType);
8524             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8525         }
8526         /* else fall thru */
8527
8528       case XBoardGame:
8529       case GNUChessGame:
8530       case PGNTag:
8531         /* Reached start of next game in file */
8532         if (appData.debugMode)
8533           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8534         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8535                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8536           case MT_NONE:
8537           case MT_CHECK:
8538             break;
8539           case MT_CHECKMATE:
8540           case MT_STAINMATE:
8541             if (WhiteOnMove(currentMove)) {
8542                 GameEnds(BlackWins, "Black mates", GE_FILE);
8543             } else {
8544                 GameEnds(WhiteWins, "White mates", GE_FILE);
8545             }
8546             break;
8547           case MT_STALEMATE:
8548             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8549             break;
8550         }
8551         done = TRUE;
8552         break;
8553
8554       case PositionDiagram:     /* should not happen; ignore */
8555       case ElapsedTime:         /* ignore */
8556       case NAG:                 /* ignore */
8557         if (appData.debugMode)
8558           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8559                   yy_text, (int) moveType);
8560         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8561
8562       case IllegalMove:
8563         if (appData.testLegality) {
8564             if (appData.debugMode)
8565               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8566             sprintf(move, _("Illegal move: %d.%s%s"),
8567                     (forwardMostMove / 2) + 1,
8568                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8569             DisplayError(move, 0);
8570             done = TRUE;
8571         } else {
8572             if (appData.debugMode)
8573               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8574                       yy_text, currentMoveString);
8575             fromX = currentMoveString[0] - AAA;
8576             fromY = currentMoveString[1] - ONE;
8577             toX = currentMoveString[2] - AAA;
8578             toY = currentMoveString[3] - ONE;
8579             promoChar = currentMoveString[4];
8580         }
8581         break;
8582
8583       case AmbiguousMove:
8584         if (appData.debugMode)
8585           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8586         sprintf(move, _("Ambiguous move: %d.%s%s"),
8587                 (forwardMostMove / 2) + 1,
8588                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8589         DisplayError(move, 0);
8590         done = TRUE;
8591         break;
8592
8593       default:
8594       case ImpossibleMove:
8595         if (appData.debugMode)
8596           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8597         sprintf(move, _("Illegal move: %d.%s%s"),
8598                 (forwardMostMove / 2) + 1,
8599                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8600         DisplayError(move, 0);
8601         done = TRUE;
8602         break;
8603     }
8604
8605     if (done) {
8606         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8607             DrawPosition(FALSE, boards[currentMove]);
8608             DisplayBothClocks();
8609             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8610               DisplayComment(currentMove - 1, commentList[currentMove]);
8611         }
8612         (void) StopLoadGameTimer();
8613         gameFileFP = NULL;
8614         cmailOldMove = forwardMostMove;
8615         return FALSE;
8616     } else {
8617         /* currentMoveString is set as a side-effect of yylex */
8618         strcat(currentMoveString, "\n");
8619         strcpy(moveList[forwardMostMove], currentMoveString);
8620         
8621         thinkOutput[0] = NULLCHAR;
8622         MakeMove(fromX, fromY, toX, toY, promoChar);
8623         currentMove = forwardMostMove;
8624         return TRUE;
8625     }
8626 }
8627
8628 /* Load the nth game from the given file */
8629 int
8630 LoadGameFromFile(filename, n, title, useList)
8631      char *filename;
8632      int n;
8633      char *title;
8634      /*Boolean*/ int useList;
8635 {
8636     FILE *f;
8637     char buf[MSG_SIZ];
8638
8639     if (strcmp(filename, "-") == 0) {
8640         f = stdin;
8641         title = "stdin";
8642     } else {
8643         f = fopen(filename, "rb");
8644         if (f == NULL) {
8645           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8646             DisplayError(buf, errno);
8647             return FALSE;
8648         }
8649     }
8650     if (fseek(f, 0, 0) == -1) {
8651         /* f is not seekable; probably a pipe */
8652         useList = FALSE;
8653     }
8654     if (useList && n == 0) {
8655         int error = GameListBuild(f);
8656         if (error) {
8657             DisplayError(_("Cannot build game list"), error);
8658         } else if (!ListEmpty(&gameList) &&
8659                    ((ListGame *) gameList.tailPred)->number > 1) {
8660             GameListPopUp(f, title);
8661             return TRUE;
8662         }
8663         GameListDestroy();
8664         n = 1;
8665     }
8666     if (n == 0) n = 1;
8667     return LoadGame(f, n, title, FALSE);
8668 }
8669
8670
8671 void
8672 MakeRegisteredMove()
8673 {
8674     int fromX, fromY, toX, toY;
8675     char promoChar;
8676     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8677         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8678           case CMAIL_MOVE:
8679           case CMAIL_DRAW:
8680             if (appData.debugMode)
8681               fprintf(debugFP, "Restoring %s for game %d\n",
8682                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8683     
8684             thinkOutput[0] = NULLCHAR;
8685             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8686             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8687             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8688             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8689             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8690             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8691             MakeMove(fromX, fromY, toX, toY, promoChar);
8692             ShowMove(fromX, fromY, toX, toY);
8693               
8694             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8695                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8696               case MT_NONE:
8697               case MT_CHECK:
8698                 break;
8699                 
8700               case MT_CHECKMATE:
8701               case MT_STAINMATE:
8702                 if (WhiteOnMove(currentMove)) {
8703                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8704                 } else {
8705                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8706                 }
8707                 break;
8708                 
8709               case MT_STALEMATE:
8710                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8711                 break;
8712             }
8713
8714             break;
8715             
8716           case CMAIL_RESIGN:
8717             if (WhiteOnMove(currentMove)) {
8718                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8719             } else {
8720                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8721             }
8722             break;
8723             
8724           case CMAIL_ACCEPT:
8725             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8726             break;
8727               
8728           default:
8729             break;
8730         }
8731     }
8732
8733     return;
8734 }
8735
8736 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8737 int
8738 CmailLoadGame(f, gameNumber, title, useList)
8739      FILE *f;
8740      int gameNumber;
8741      char *title;
8742      int useList;
8743 {
8744     int retVal;
8745
8746     if (gameNumber > nCmailGames) {
8747         DisplayError(_("No more games in this message"), 0);
8748         return FALSE;
8749     }
8750     if (f == lastLoadGameFP) {
8751         int offset = gameNumber - lastLoadGameNumber;
8752         if (offset == 0) {
8753             cmailMsg[0] = NULLCHAR;
8754             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8755                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8756                 nCmailMovesRegistered--;
8757             }
8758             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8759             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8760                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8761             }
8762         } else {
8763             if (! RegisterMove()) return FALSE;
8764         }
8765     }
8766
8767     retVal = LoadGame(f, gameNumber, title, useList);
8768
8769     /* Make move registered during previous look at this game, if any */
8770     MakeRegisteredMove();
8771
8772     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8773         commentList[currentMove]
8774           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8775         DisplayComment(currentMove - 1, commentList[currentMove]);
8776     }
8777
8778     return retVal;
8779 }
8780
8781 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8782 int
8783 ReloadGame(offset)
8784      int offset;
8785 {
8786     int gameNumber = lastLoadGameNumber + offset;
8787     if (lastLoadGameFP == NULL) {
8788         DisplayError(_("No game has been loaded yet"), 0);
8789         return FALSE;
8790     }
8791     if (gameNumber <= 0) {
8792         DisplayError(_("Can't back up any further"), 0);
8793         return FALSE;
8794     }
8795     if (cmailMsgLoaded) {
8796         return CmailLoadGame(lastLoadGameFP, gameNumber,
8797                              lastLoadGameTitle, lastLoadGameUseList);
8798     } else {
8799         return LoadGame(lastLoadGameFP, gameNumber,
8800                         lastLoadGameTitle, lastLoadGameUseList);
8801     }
8802 }
8803
8804
8805
8806 /* Load the nth game from open file f */
8807 int
8808 LoadGame(f, gameNumber, title, useList)
8809      FILE *f;
8810      int gameNumber;
8811      char *title;
8812      int useList;
8813 {
8814     ChessMove cm;
8815     char buf[MSG_SIZ];
8816     int gn = gameNumber;
8817     ListGame *lg = NULL;
8818     int numPGNTags = 0;
8819     int err;
8820     GameMode oldGameMode;
8821     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8822
8823     if (appData.debugMode) 
8824         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8825
8826     if (gameMode == Training )
8827         SetTrainingModeOff();
8828
8829     oldGameMode = gameMode;
8830     if (gameMode != BeginningOfGame) {
8831       Reset(FALSE, TRUE);
8832     }
8833
8834     gameFileFP = f;
8835     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8836         fclose(lastLoadGameFP);
8837     }
8838
8839     if (useList) {
8840         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8841         
8842         if (lg) {
8843             fseek(f, lg->offset, 0);
8844             GameListHighlight(gameNumber);
8845             gn = 1;
8846         }
8847         else {
8848             DisplayError(_("Game number out of range"), 0);
8849             return FALSE;
8850         }
8851     } else {
8852         GameListDestroy();
8853         if (fseek(f, 0, 0) == -1) {
8854             if (f == lastLoadGameFP ?
8855                 gameNumber == lastLoadGameNumber + 1 :
8856                 gameNumber == 1) {
8857                 gn = 1;
8858             } else {
8859                 DisplayError(_("Can't seek on game file"), 0);
8860                 return FALSE;
8861             }
8862         }
8863     }
8864     lastLoadGameFP = f;
8865     lastLoadGameNumber = gameNumber;
8866     strcpy(lastLoadGameTitle, title);
8867     lastLoadGameUseList = useList;
8868
8869     yynewfile(f);
8870
8871     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8872       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8873                 lg->gameInfo.black);
8874             DisplayTitle(buf);
8875     } else if (*title != NULLCHAR) {
8876         if (gameNumber > 1) {
8877             sprintf(buf, "%s %d", title, gameNumber);
8878             DisplayTitle(buf);
8879         } else {
8880             DisplayTitle(title);
8881         }
8882     }
8883
8884     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8885         gameMode = PlayFromGameFile;
8886         ModeHighlight();
8887     }
8888
8889     currentMove = forwardMostMove = backwardMostMove = 0;
8890     CopyBoard(boards[0], initialPosition);
8891     StopClocks();
8892
8893     /*
8894      * Skip the first gn-1 games in the file.
8895      * Also skip over anything that precedes an identifiable 
8896      * start of game marker, to avoid being confused by 
8897      * garbage at the start of the file.  Currently 
8898      * recognized start of game markers are the move number "1",
8899      * the pattern "gnuchess .* game", the pattern
8900      * "^[#;%] [^ ]* game file", and a PGN tag block.  
8901      * A game that starts with one of the latter two patterns
8902      * will also have a move number 1, possibly
8903      * following a position diagram.
8904      * 5-4-02: Let's try being more lenient and allowing a game to
8905      * start with an unnumbered move.  Does that break anything?
8906      */
8907     cm = lastLoadGameStart = (ChessMove) 0;
8908     while (gn > 0) {
8909         yyboardindex = forwardMostMove;
8910         cm = (ChessMove) yylex();
8911         switch (cm) {
8912           case (ChessMove) 0:
8913             if (cmailMsgLoaded) {
8914                 nCmailGames = CMAIL_MAX_GAMES - gn;
8915             } else {
8916                 Reset(TRUE, TRUE);
8917                 DisplayError(_("Game not found in file"), 0);
8918             }
8919             return FALSE;
8920
8921           case GNUChessGame:
8922           case XBoardGame:
8923             gn--;
8924             lastLoadGameStart = cm;
8925             break;
8926             
8927           case MoveNumberOne:
8928             switch (lastLoadGameStart) {
8929               case GNUChessGame:
8930               case XBoardGame:
8931               case PGNTag:
8932                 break;
8933               case MoveNumberOne:
8934               case (ChessMove) 0:
8935                 gn--;           /* count this game */
8936                 lastLoadGameStart = cm;
8937                 break;
8938               default:
8939                 /* impossible */
8940                 break;
8941             }
8942             break;
8943
8944           case PGNTag:
8945             switch (lastLoadGameStart) {
8946               case GNUChessGame:
8947               case PGNTag:
8948               case MoveNumberOne:
8949               case (ChessMove) 0:
8950                 gn--;           /* count this game */
8951                 lastLoadGameStart = cm;
8952                 break;
8953               case XBoardGame:
8954                 lastLoadGameStart = cm; /* game counted already */
8955                 break;
8956               default:
8957                 /* impossible */
8958                 break;
8959             }
8960             if (gn > 0) {
8961                 do {
8962                     yyboardindex = forwardMostMove;
8963                     cm = (ChessMove) yylex();
8964                 } while (cm == PGNTag || cm == Comment);
8965             }
8966             break;
8967
8968           case WhiteWins:
8969           case BlackWins:
8970           case GameIsDrawn:
8971             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8972                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
8973                     != CMAIL_OLD_RESULT) {
8974                     nCmailResults ++ ;
8975                     cmailResult[  CMAIL_MAX_GAMES
8976                                 - gn - 1] = CMAIL_OLD_RESULT;
8977                 }
8978             }
8979             break;
8980
8981           case NormalMove:
8982             /* Only a NormalMove can be at the start of a game
8983              * without a position diagram. */
8984             if (lastLoadGameStart == (ChessMove) 0) {
8985               gn--;
8986               lastLoadGameStart = MoveNumberOne;
8987             }
8988             break;
8989
8990           default:
8991             break;
8992         }
8993     }
8994     
8995     if (appData.debugMode)
8996       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8997
8998     if (cm == XBoardGame) {
8999         /* Skip any header junk before position diagram and/or move 1 */
9000         for (;;) {
9001             yyboardindex = forwardMostMove;
9002             cm = (ChessMove) yylex();
9003
9004             if (cm == (ChessMove) 0 ||
9005                 cm == GNUChessGame || cm == XBoardGame) {
9006                 /* Empty game; pretend end-of-file and handle later */
9007                 cm = (ChessMove) 0;
9008                 break;
9009             }
9010
9011             if (cm == MoveNumberOne || cm == PositionDiagram ||
9012                 cm == PGNTag || cm == Comment)
9013               break;
9014         }
9015     } else if (cm == GNUChessGame) {
9016         if (gameInfo.event != NULL) {
9017             free(gameInfo.event);
9018         }
9019         gameInfo.event = StrSave(yy_text);
9020     }   
9021
9022     startedFromSetupPosition = FALSE;
9023     while (cm == PGNTag) {
9024         if (appData.debugMode) 
9025           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9026         err = ParsePGNTag(yy_text, &gameInfo);
9027         if (!err) numPGNTags++;
9028
9029         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9030         if(gameInfo.variant != oldVariant) {
9031             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9032             InitPosition(TRUE);
9033             oldVariant = gameInfo.variant;
9034             if (appData.debugMode) 
9035               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9036         }
9037
9038
9039         if (gameInfo.fen != NULL) {
9040           Board initial_position;
9041           startedFromSetupPosition = TRUE;
9042           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9043             Reset(TRUE, TRUE);
9044             DisplayError(_("Bad FEN position in file"), 0);
9045             return FALSE;
9046           }
9047           CopyBoard(boards[0], initial_position);
9048           if (blackPlaysFirst) {
9049             currentMove = forwardMostMove = backwardMostMove = 1;
9050             CopyBoard(boards[1], initial_position);
9051             strcpy(moveList[0], "");
9052             strcpy(parseList[0], "");
9053             timeRemaining[0][1] = whiteTimeRemaining;
9054             timeRemaining[1][1] = blackTimeRemaining;
9055             if (commentList[0] != NULL) {
9056               commentList[1] = commentList[0];
9057               commentList[0] = NULL;
9058             }
9059           } else {
9060             currentMove = forwardMostMove = backwardMostMove = 0;
9061           }
9062           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9063           {   int i;
9064               initialRulePlies = FENrulePlies;
9065               epStatus[forwardMostMove] = FENepStatus;
9066               for( i=0; i< nrCastlingRights; i++ )
9067                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9068           }
9069           yyboardindex = forwardMostMove;
9070           free(gameInfo.fen);
9071           gameInfo.fen = NULL;
9072         }
9073
9074         yyboardindex = forwardMostMove;
9075         cm = (ChessMove) yylex();
9076
9077         /* Handle comments interspersed among the tags */
9078         while (cm == Comment) {
9079             char *p;
9080             if (appData.debugMode) 
9081               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9082             p = yy_text;
9083             if (*p == '{' || *p == '[' || *p == '(') {
9084                 p[strlen(p) - 1] = NULLCHAR;
9085                 p++;
9086             }
9087             while (*p == '\n') p++;
9088             AppendComment(currentMove, p);
9089             yyboardindex = forwardMostMove;
9090             cm = (ChessMove) yylex();
9091         }
9092     }
9093
9094     /* don't rely on existence of Event tag since if game was
9095      * pasted from clipboard the Event tag may not exist
9096      */
9097     if (numPGNTags > 0){
9098         char *tags;
9099         if (gameInfo.variant == VariantNormal) {
9100           gameInfo.variant = StringToVariant(gameInfo.event);
9101         }
9102         if (!matchMode) {
9103           if( appData.autoDisplayTags ) {
9104             tags = PGNTags(&gameInfo);
9105             TagsPopUp(tags, CmailMsg());
9106             free(tags);
9107           }
9108         }
9109     } else {
9110         /* Make something up, but don't display it now */
9111         SetGameInfo();
9112         TagsPopDown();
9113     }
9114
9115     if (cm == PositionDiagram) {
9116         int i, j;
9117         char *p;
9118         Board initial_position;
9119
9120         if (appData.debugMode)
9121           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9122
9123         if (!startedFromSetupPosition) {
9124             p = yy_text;
9125             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9126               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9127                 switch (*p) {
9128                   case '[':
9129                   case '-':
9130                   case ' ':
9131                   case '\t':
9132                   case '\n':
9133                   case '\r':
9134                     break;
9135                   default:
9136                     initial_position[i][j++] = CharToPiece(*p);
9137                     break;
9138                 }
9139             while (*p == ' ' || *p == '\t' ||
9140                    *p == '\n' || *p == '\r') p++;
9141         
9142             if (strncmp(p, "black", strlen("black"))==0)
9143               blackPlaysFirst = TRUE;
9144             else
9145               blackPlaysFirst = FALSE;
9146             startedFromSetupPosition = TRUE;
9147         
9148             CopyBoard(boards[0], initial_position);
9149             if (blackPlaysFirst) {
9150                 currentMove = forwardMostMove = backwardMostMove = 1;
9151                 CopyBoard(boards[1], initial_position);
9152                 strcpy(moveList[0], "");
9153                 strcpy(parseList[0], "");
9154                 timeRemaining[0][1] = whiteTimeRemaining;
9155                 timeRemaining[1][1] = blackTimeRemaining;
9156                 if (commentList[0] != NULL) {
9157                     commentList[1] = commentList[0];
9158                     commentList[0] = NULL;
9159                 }
9160             } else {
9161                 currentMove = forwardMostMove = backwardMostMove = 0;
9162             }
9163         }
9164         yyboardindex = forwardMostMove;
9165         cm = (ChessMove) yylex();
9166     }
9167
9168     if (first.pr == NoProc) {
9169         StartChessProgram(&first);
9170     }
9171     InitChessProgram(&first, FALSE);
9172     SendToProgram("force\n", &first);
9173     if (startedFromSetupPosition) {
9174         SendBoard(&first, forwardMostMove);
9175     if (appData.debugMode) {
9176         fprintf(debugFP, "Load Game\n");
9177     }
9178         DisplayBothClocks();
9179     }      
9180
9181     /* [HGM] server: flag to write setup moves in broadcast file as one */
9182     loadFlag = appData.suppressLoadMoves;
9183
9184     while (cm == Comment) {
9185         char *p;
9186         if (appData.debugMode) 
9187           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9188         p = yy_text;
9189         if (*p == '{' || *p == '[' || *p == '(') {
9190             p[strlen(p) - 1] = NULLCHAR;
9191             p++;
9192         }
9193         while (*p == '\n') p++;
9194         AppendComment(currentMove, p);
9195         yyboardindex = forwardMostMove;
9196         cm = (ChessMove) yylex();
9197     }
9198
9199     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9200         cm == WhiteWins || cm == BlackWins ||
9201         cm == GameIsDrawn || cm == GameUnfinished) {
9202         DisplayMessage("", _("No moves in game"));
9203         if (cmailMsgLoaded) {
9204             if (appData.debugMode)
9205               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9206             ClearHighlights();
9207             flipView = FALSE;
9208         }
9209         DrawPosition(FALSE, boards[currentMove]);
9210         DisplayBothClocks();
9211         gameMode = EditGame;
9212         ModeHighlight();
9213         gameFileFP = NULL;
9214         cmailOldMove = 0;
9215         return TRUE;
9216     }
9217
9218     // [HGM] PV info: routine tests if comment empty
9219     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9220         DisplayComment(currentMove - 1, commentList[currentMove]);
9221     }
9222     if (!matchMode && appData.timeDelay != 0) 
9223       DrawPosition(FALSE, boards[currentMove]);
9224
9225     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9226       programStats.ok_to_send = 1;
9227     }
9228
9229     /* if the first token after the PGN tags is a move
9230      * and not move number 1, retrieve it from the parser 
9231      */
9232     if (cm != MoveNumberOne)
9233         LoadGameOneMove(cm);
9234
9235     /* load the remaining moves from the file */
9236     while (LoadGameOneMove((ChessMove)0)) {
9237       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9238       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9239     }
9240
9241     /* rewind to the start of the game */
9242     currentMove = backwardMostMove;
9243
9244     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9245
9246     if (oldGameMode == AnalyzeFile ||
9247         oldGameMode == AnalyzeMode) {
9248       AnalyzeFileEvent();
9249     }
9250
9251     if (matchMode || appData.timeDelay == 0) {
9252       ToEndEvent();
9253       gameMode = EditGame;
9254       ModeHighlight();
9255     } else if (appData.timeDelay > 0) {
9256       AutoPlayGameLoop();
9257     }
9258
9259     if (appData.debugMode) 
9260         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9261
9262     loadFlag = 0; /* [HGM] true game starts */
9263     return TRUE;
9264 }
9265
9266 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9267 int
9268 ReloadPosition(offset)
9269      int offset;
9270 {
9271     int positionNumber = lastLoadPositionNumber + offset;
9272     if (lastLoadPositionFP == NULL) {
9273         DisplayError(_("No position has been loaded yet"), 0);
9274         return FALSE;
9275     }
9276     if (positionNumber <= 0) {
9277         DisplayError(_("Can't back up any further"), 0);
9278         return FALSE;
9279     }
9280     return LoadPosition(lastLoadPositionFP, positionNumber,
9281                         lastLoadPositionTitle);
9282 }
9283
9284 /* Load the nth position from the given file */
9285 int
9286 LoadPositionFromFile(filename, n, title)
9287      char *filename;
9288      int n;
9289      char *title;
9290 {
9291     FILE *f;
9292     char buf[MSG_SIZ];
9293
9294     if (strcmp(filename, "-") == 0) {
9295         return LoadPosition(stdin, n, "stdin");
9296     } else {
9297         f = fopen(filename, "rb");
9298         if (f == NULL) {
9299             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9300             DisplayError(buf, errno);
9301             return FALSE;
9302         } else {
9303             return LoadPosition(f, n, title);
9304         }
9305     }
9306 }
9307
9308 /* Load the nth position from the given open file, and close it */
9309 int
9310 LoadPosition(f, positionNumber, title)
9311      FILE *f;
9312      int positionNumber;
9313      char *title;
9314 {
9315     char *p, line[MSG_SIZ];
9316     Board initial_position;
9317     int i, j, fenMode, pn;
9318     
9319     if (gameMode == Training )
9320         SetTrainingModeOff();
9321
9322     if (gameMode != BeginningOfGame) {
9323         Reset(FALSE, TRUE);
9324     }
9325     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9326         fclose(lastLoadPositionFP);
9327     }
9328     if (positionNumber == 0) positionNumber = 1;
9329     lastLoadPositionFP = f;
9330     lastLoadPositionNumber = positionNumber;
9331     strcpy(lastLoadPositionTitle, title);
9332     if (first.pr == NoProc) {
9333       StartChessProgram(&first);
9334       InitChessProgram(&first, FALSE);
9335     }    
9336     pn = positionNumber;
9337     if (positionNumber < 0) {
9338         /* Negative position number means to seek to that byte offset */
9339         if (fseek(f, -positionNumber, 0) == -1) {
9340             DisplayError(_("Can't seek on position file"), 0);
9341             return FALSE;
9342         };
9343         pn = 1;
9344     } else {
9345         if (fseek(f, 0, 0) == -1) {
9346             if (f == lastLoadPositionFP ?
9347                 positionNumber == lastLoadPositionNumber + 1 :
9348                 positionNumber == 1) {
9349                 pn = 1;
9350             } else {
9351                 DisplayError(_("Can't seek on position file"), 0);
9352                 return FALSE;
9353             }
9354         }
9355     }
9356     /* See if this file is FEN or old-style xboard */
9357     if (fgets(line, MSG_SIZ, f) == NULL) {
9358         DisplayError(_("Position not found in file"), 0);
9359         return FALSE;
9360     }
9361 #if 0
9362     switch (line[0]) {
9363       case '#':  case 'x':
9364       default:
9365         fenMode = FALSE;
9366         break;
9367       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':
9368       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':
9369       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':
9370       case '7':  case '8':  case '9':
9371       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':
9372       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':
9373       case 'C':  case 'W':             case 'c':  case 'w': 
9374         fenMode = TRUE;
9375         break;
9376     }
9377 #else
9378     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9379     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9380 #endif
9381
9382     if (pn >= 2) {
9383         if (fenMode || line[0] == '#') pn--;
9384         while (pn > 0) {
9385             /* skip positions before number pn */
9386             if (fgets(line, MSG_SIZ, f) == NULL) {
9387                 Reset(TRUE, TRUE);
9388                 DisplayError(_("Position not found in file"), 0);
9389                 return FALSE;
9390             }
9391             if (fenMode || line[0] == '#') pn--;
9392         }
9393     }
9394
9395     if (fenMode) {
9396         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9397             DisplayError(_("Bad FEN position in file"), 0);
9398             return FALSE;
9399         }
9400     } else {
9401         (void) fgets(line, MSG_SIZ, f);
9402         (void) fgets(line, MSG_SIZ, f);
9403     
9404         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9405             (void) fgets(line, MSG_SIZ, f);
9406             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9407                 if (*p == ' ')
9408                   continue;
9409                 initial_position[i][j++] = CharToPiece(*p);
9410             }
9411         }
9412     
9413         blackPlaysFirst = FALSE;
9414         if (!feof(f)) {
9415             (void) fgets(line, MSG_SIZ, f);
9416             if (strncmp(line, "black", strlen("black"))==0)
9417               blackPlaysFirst = TRUE;
9418         }
9419     }
9420     startedFromSetupPosition = TRUE;
9421     
9422     SendToProgram("force\n", &first);
9423     CopyBoard(boards[0], initial_position);
9424     if (blackPlaysFirst) {
9425         currentMove = forwardMostMove = backwardMostMove = 1;
9426         strcpy(moveList[0], "");
9427         strcpy(parseList[0], "");
9428         CopyBoard(boards[1], initial_position);
9429         DisplayMessage("", _("Black to play"));
9430     } else {
9431         currentMove = forwardMostMove = backwardMostMove = 0;
9432         DisplayMessage("", _("White to play"));
9433     }
9434           /* [HGM] copy FEN attributes as well */
9435           {   int i;
9436               initialRulePlies = FENrulePlies;
9437               epStatus[forwardMostMove] = FENepStatus;
9438               for( i=0; i< nrCastlingRights; i++ )
9439                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9440           }
9441     SendBoard(&first, forwardMostMove);
9442     if (appData.debugMode) {
9443 int i, j;
9444   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9445   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9446         fprintf(debugFP, "Load Position\n");
9447     }
9448
9449     if (positionNumber > 1) {
9450         sprintf(line, "%s %d", title, positionNumber);
9451         DisplayTitle(line);
9452     } else {
9453         DisplayTitle(title);
9454     }
9455     gameMode = EditGame;
9456     ModeHighlight();
9457     ResetClocks();
9458     timeRemaining[0][1] = whiteTimeRemaining;
9459     timeRemaining[1][1] = blackTimeRemaining;
9460     DrawPosition(FALSE, boards[currentMove]);
9461    
9462     return TRUE;
9463 }
9464
9465
9466 void
9467 CopyPlayerNameIntoFileName(dest, src)
9468      char **dest, *src;
9469 {
9470     while (*src != NULLCHAR && *src != ',') {
9471         if (*src == ' ') {
9472             *(*dest)++ = '_';
9473             src++;
9474         } else {
9475             *(*dest)++ = *src++;
9476         }
9477     }
9478 }
9479
9480 char *DefaultFileName(ext)
9481      char *ext;
9482 {
9483     static char def[MSG_SIZ];
9484     char *p;
9485
9486     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9487         p = def;
9488         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9489         *p++ = '-';
9490         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9491         *p++ = '.';
9492         strcpy(p, ext);
9493     } else {
9494         def[0] = NULLCHAR;
9495     }
9496     return def;
9497 }
9498
9499 /* Save the current game to the given file */
9500 int
9501 SaveGameToFile(filename, append)
9502      char *filename;
9503      int append;
9504 {
9505     FILE *f;
9506     char buf[MSG_SIZ];
9507
9508     if (strcmp(filename, "-") == 0) {
9509         return SaveGame(stdout, 0, NULL);
9510     } else {
9511         f = fopen(filename, append ? "a" : "w");
9512         if (f == NULL) {
9513             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9514             DisplayError(buf, errno);
9515             return FALSE;
9516         } else {
9517             return SaveGame(f, 0, NULL);
9518         }
9519     }
9520 }
9521
9522 char *
9523 SavePart(str)
9524      char *str;
9525 {
9526     static char buf[MSG_SIZ];
9527     char *p;
9528     
9529     p = strchr(str, ' ');
9530     if (p == NULL) return str;
9531     strncpy(buf, str, p - str);
9532     buf[p - str] = NULLCHAR;
9533     return buf;
9534 }
9535
9536 #define PGN_MAX_LINE 75
9537
9538 #define PGN_SIDE_WHITE  0
9539 #define PGN_SIDE_BLACK  1
9540
9541 /* [AS] */
9542 static int FindFirstMoveOutOfBook( int side )
9543 {
9544     int result = -1;
9545
9546     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9547         int index = backwardMostMove;
9548         int has_book_hit = 0;
9549
9550         if( (index % 2) != side ) {
9551             index++;
9552         }
9553
9554         while( index < forwardMostMove ) {
9555             /* Check to see if engine is in book */
9556             int depth = pvInfoList[index].depth;
9557             int score = pvInfoList[index].score;
9558             int in_book = 0;
9559
9560             if( depth <= 2 ) {
9561                 in_book = 1;
9562             }
9563             else if( score == 0 && depth == 63 ) {
9564                 in_book = 1; /* Zappa */
9565             }
9566             else if( score == 2 && depth == 99 ) {
9567                 in_book = 1; /* Abrok */
9568             }
9569
9570             has_book_hit += in_book;
9571
9572             if( ! in_book ) {
9573                 result = index;
9574
9575                 break;
9576             }
9577
9578             index += 2;
9579         }
9580     }
9581
9582     return result;
9583 }
9584
9585 /* [AS] */
9586 void GetOutOfBookInfo( char * buf )
9587 {
9588     int oob[2];
9589     int i;
9590     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9591
9592     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9593     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9594
9595     *buf = '\0';
9596
9597     if( oob[0] >= 0 || oob[1] >= 0 ) {
9598         for( i=0; i<2; i++ ) {
9599             int idx = oob[i];
9600
9601             if( idx >= 0 ) {
9602                 if( i > 0 && oob[0] >= 0 ) {
9603                     strcat( buf, "   " );
9604                 }
9605
9606                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9607                 sprintf( buf+strlen(buf), "%s%.2f", 
9608                     pvInfoList[idx].score >= 0 ? "+" : "",
9609                     pvInfoList[idx].score / 100.0 );
9610             }
9611         }
9612     }
9613 }
9614
9615 /* Save game in PGN style and close the file */
9616 int
9617 SaveGamePGN(f)
9618      FILE *f;
9619 {
9620     int i, offset, linelen, newblock;
9621     time_t tm;
9622 //    char *movetext;
9623     char numtext[32];
9624     int movelen, numlen, blank;
9625     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9626
9627     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9628     
9629     tm = time((time_t *) NULL);
9630     
9631     PrintPGNTags(f, &gameInfo);
9632     
9633     if (backwardMostMove > 0 || startedFromSetupPosition) {
9634         char *fen = PositionToFEN(backwardMostMove, NULL);
9635         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9636         fprintf(f, "\n{--------------\n");
9637         PrintPosition(f, backwardMostMove);
9638         fprintf(f, "--------------}\n");
9639         free(fen);
9640     }
9641     else {
9642         /* [AS] Out of book annotation */
9643         if( appData.saveOutOfBookInfo ) {
9644             char buf[64];
9645
9646             GetOutOfBookInfo( buf );
9647
9648             if( buf[0] != '\0' ) {
9649                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9650             }
9651         }
9652
9653         fprintf(f, "\n");
9654     }
9655
9656     i = backwardMostMove;
9657     linelen = 0;
9658     newblock = TRUE;
9659
9660     while (i < forwardMostMove) {
9661         /* Print comments preceding this move */
9662         if (commentList[i] != NULL) {
9663             if (linelen > 0) fprintf(f, "\n");
9664             fprintf(f, "{\n%s}\n", commentList[i]);
9665             linelen = 0;
9666             newblock = TRUE;
9667         }
9668
9669         /* Format move number */
9670         if ((i % 2) == 0) {
9671             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9672         } else {
9673             if (newblock) {
9674                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9675             } else {
9676                 numtext[0] = NULLCHAR;
9677             }
9678         }
9679         numlen = strlen(numtext);
9680         newblock = FALSE;
9681
9682         /* Print move number */
9683         blank = linelen > 0 && numlen > 0;
9684         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9685             fprintf(f, "\n");
9686             linelen = 0;
9687             blank = 0;
9688         }
9689         if (blank) {
9690             fprintf(f, " ");
9691             linelen++;
9692         }
9693         fprintf(f, numtext);
9694         linelen += numlen;
9695
9696         /* Get move */
9697         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9698         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9699 #if 0
9700         // SavePart already does this!
9701         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9702                 int p = movelen - 1;
9703                 if(move_buffer[p] == ' ') p--;
9704                 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
9705                     while(p && move_buffer[--p] != '(');
9706                     if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
9707                 }
9708         }
9709 #endif
9710         /* Print move */
9711         blank = linelen > 0 && movelen > 0;
9712         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9713             fprintf(f, "\n");
9714             linelen = 0;
9715             blank = 0;
9716         }
9717         if (blank) {
9718             fprintf(f, " ");
9719             linelen++;
9720         }
9721         fprintf(f, move_buffer);
9722         linelen += movelen;
9723
9724         /* [AS] Add PV info if present */
9725         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9726             /* [HGM] add time */
9727             char buf[MSG_SIZ]; int seconds = 0;
9728
9729 #if 1
9730             if(i >= backwardMostMove) {
9731                 if(WhiteOnMove(i))
9732                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9733                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9734                 else
9735                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9736                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9737             }
9738             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9739 #else
9740             seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
9741 #endif
9742
9743             if( seconds <= 0) buf[0] = 0; else
9744             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9745                 seconds = (seconds + 4)/10; // round to full seconds
9746                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9747                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9748             }
9749
9750             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9751                 pvInfoList[i].score >= 0 ? "+" : "",
9752                 pvInfoList[i].score / 100.0,
9753                 pvInfoList[i].depth,
9754                 buf );
9755
9756             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9757
9758             /* Print score/depth */
9759             blank = linelen > 0 && movelen > 0;
9760             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9761                 fprintf(f, "\n");
9762                 linelen = 0;
9763                 blank = 0;
9764             }
9765             if (blank) {
9766                 fprintf(f, " ");
9767                 linelen++;
9768             }
9769             fprintf(f, move_buffer);
9770             linelen += movelen;
9771         }
9772
9773         i++;
9774     }
9775     
9776     /* Start a new line */
9777     if (linelen > 0) fprintf(f, "\n");
9778
9779     /* Print comments after last move */
9780     if (commentList[i] != NULL) {
9781         fprintf(f, "{\n%s}\n", commentList[i]);
9782     }
9783
9784     /* Print result */
9785     if (gameInfo.resultDetails != NULL &&
9786         gameInfo.resultDetails[0] != NULLCHAR) {
9787         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9788                 PGNResult(gameInfo.result));
9789     } else {
9790         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9791     }
9792
9793     fclose(f);
9794     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9795     return TRUE;
9796 }
9797
9798 /* Save game in old style and close the file */
9799 int
9800 SaveGameOldStyle(f)
9801      FILE *f;
9802 {
9803     int i, offset;
9804     time_t tm;
9805     
9806     tm = time((time_t *) NULL);
9807     
9808     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9809     PrintOpponents(f);
9810     
9811     if (backwardMostMove > 0 || startedFromSetupPosition) {
9812         fprintf(f, "\n[--------------\n");
9813         PrintPosition(f, backwardMostMove);
9814         fprintf(f, "--------------]\n");
9815     } else {
9816         fprintf(f, "\n");
9817     }
9818
9819     i = backwardMostMove;
9820     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9821
9822     while (i < forwardMostMove) {
9823         if (commentList[i] != NULL) {
9824             fprintf(f, "[%s]\n", commentList[i]);
9825         }
9826
9827         if ((i % 2) == 1) {
9828             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
9829             i++;
9830         } else {
9831             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
9832             i++;
9833             if (commentList[i] != NULL) {
9834                 fprintf(f, "\n");
9835                 continue;
9836             }
9837             if (i >= forwardMostMove) {
9838                 fprintf(f, "\n");
9839                 break;
9840             }
9841             fprintf(f, "%s\n", parseList[i]);
9842             i++;
9843         }
9844     }
9845     
9846     if (commentList[i] != NULL) {
9847         fprintf(f, "[%s]\n", commentList[i]);
9848     }
9849
9850     /* This isn't really the old style, but it's close enough */
9851     if (gameInfo.resultDetails != NULL &&
9852         gameInfo.resultDetails[0] != NULLCHAR) {
9853         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9854                 gameInfo.resultDetails);
9855     } else {
9856         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9857     }
9858
9859     fclose(f);
9860     return TRUE;
9861 }
9862
9863 /* Save the current game to open file f and close the file */
9864 int
9865 SaveGame(f, dummy, dummy2)
9866      FILE *f;
9867      int dummy;
9868      char *dummy2;
9869 {
9870     if (gameMode == EditPosition) EditPositionDone();
9871     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9872     if (appData.oldSaveStyle)
9873       return SaveGameOldStyle(f);
9874     else
9875       return SaveGamePGN(f);
9876 }
9877
9878 /* Save the current position to the given file */
9879 int
9880 SavePositionToFile(filename)
9881      char *filename;
9882 {
9883     FILE *f;
9884     char buf[MSG_SIZ];
9885
9886     if (strcmp(filename, "-") == 0) {
9887         return SavePosition(stdout, 0, NULL);
9888     } else {
9889         f = fopen(filename, "a");
9890         if (f == NULL) {
9891             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9892             DisplayError(buf, errno);
9893             return FALSE;
9894         } else {
9895             SavePosition(f, 0, NULL);
9896             return TRUE;
9897         }
9898     }
9899 }
9900
9901 /* Save the current position to the given open file and close the file */
9902 int
9903 SavePosition(f, dummy, dummy2)
9904      FILE *f;
9905      int dummy;
9906      char *dummy2;
9907 {
9908     time_t tm;
9909     char *fen;
9910     
9911     if (appData.oldSaveStyle) {
9912         tm = time((time_t *) NULL);
9913     
9914         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9915         PrintOpponents(f);
9916         fprintf(f, "[--------------\n");
9917         PrintPosition(f, currentMove);
9918         fprintf(f, "--------------]\n");
9919     } else {
9920         fen = PositionToFEN(currentMove, NULL);
9921         fprintf(f, "%s\n", fen);
9922         free(fen);
9923     }
9924     fclose(f);
9925     return TRUE;
9926 }
9927
9928 void
9929 ReloadCmailMsgEvent(unregister)
9930      int unregister;
9931 {
9932 #if !WIN32
9933     static char *inFilename = NULL;
9934     static char *outFilename;
9935     int i;
9936     struct stat inbuf, outbuf;
9937     int status;
9938     
9939     /* Any registered moves are unregistered if unregister is set, */
9940     /* i.e. invoked by the signal handler */
9941     if (unregister) {
9942         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9943             cmailMoveRegistered[i] = FALSE;
9944             if (cmailCommentList[i] != NULL) {
9945                 free(cmailCommentList[i]);
9946                 cmailCommentList[i] = NULL;
9947             }
9948         }
9949         nCmailMovesRegistered = 0;
9950     }
9951
9952     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9953         cmailResult[i] = CMAIL_NOT_RESULT;
9954     }
9955     nCmailResults = 0;
9956
9957     if (inFilename == NULL) {
9958         /* Because the filenames are static they only get malloced once  */
9959         /* and they never get freed                                      */
9960         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9961         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9962
9963         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9964         sprintf(outFilename, "%s.out", appData.cmailGameName);
9965     }
9966     
9967     status = stat(outFilename, &outbuf);
9968     if (status < 0) {
9969         cmailMailedMove = FALSE;
9970     } else {
9971         status = stat(inFilename, &inbuf);
9972         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9973     }
9974     
9975     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9976        counts the games, notes how each one terminated, etc.
9977        
9978        It would be nice to remove this kludge and instead gather all
9979        the information while building the game list.  (And to keep it
9980        in the game list nodes instead of having a bunch of fixed-size
9981        parallel arrays.)  Note this will require getting each game's
9982        termination from the PGN tags, as the game list builder does
9983        not process the game moves.  --mann
9984        */
9985     cmailMsgLoaded = TRUE;
9986     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9987     
9988     /* Load first game in the file or popup game menu */
9989     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9990
9991 #endif /* !WIN32 */
9992     return;
9993 }
9994
9995 int
9996 RegisterMove()
9997 {
9998     FILE *f;
9999     char string[MSG_SIZ];
10000
10001     if (   cmailMailedMove
10002         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10003         return TRUE;            /* Allow free viewing  */
10004     }
10005
10006     /* Unregister move to ensure that we don't leave RegisterMove        */
10007     /* with the move registered when the conditions for registering no   */
10008     /* longer hold                                                       */
10009     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10010         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10011         nCmailMovesRegistered --;
10012
10013         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10014           {
10015               free(cmailCommentList[lastLoadGameNumber - 1]);
10016               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10017           }
10018     }
10019
10020     if (cmailOldMove == -1) {
10021         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10022         return FALSE;
10023     }
10024
10025     if (currentMove > cmailOldMove + 1) {
10026         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10027         return FALSE;
10028     }
10029
10030     if (currentMove < cmailOldMove) {
10031         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10032         return FALSE;
10033     }
10034
10035     if (forwardMostMove > currentMove) {
10036         /* Silently truncate extra moves */
10037         TruncateGame();
10038     }
10039
10040     if (   (currentMove == cmailOldMove + 1)
10041         || (   (currentMove == cmailOldMove)
10042             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10043                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10044         if (gameInfo.result != GameUnfinished) {
10045             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10046         }
10047
10048         if (commentList[currentMove] != NULL) {
10049             cmailCommentList[lastLoadGameNumber - 1]
10050               = StrSave(commentList[currentMove]);
10051         }
10052         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10053
10054         if (appData.debugMode)
10055           fprintf(debugFP, "Saving %s for game %d\n",
10056                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10057
10058         sprintf(string,
10059                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10060         
10061         f = fopen(string, "w");
10062         if (appData.oldSaveStyle) {
10063             SaveGameOldStyle(f); /* also closes the file */
10064             
10065             sprintf(string, "%s.pos.out", appData.cmailGameName);
10066             f = fopen(string, "w");
10067             SavePosition(f, 0, NULL); /* also closes the file */
10068         } else {
10069             fprintf(f, "{--------------\n");
10070             PrintPosition(f, currentMove);
10071             fprintf(f, "--------------}\n\n");
10072             
10073             SaveGame(f, 0, NULL); /* also closes the file*/
10074         }
10075         
10076         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10077         nCmailMovesRegistered ++;
10078     } else if (nCmailGames == 1) {
10079         DisplayError(_("You have not made a move yet"), 0);
10080         return FALSE;
10081     }
10082
10083     return TRUE;
10084 }
10085
10086 void
10087 MailMoveEvent()
10088 {
10089 #if !WIN32
10090     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10091     FILE *commandOutput;
10092     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10093     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10094     int nBuffers;
10095     int i;
10096     int archived;
10097     char *arcDir;
10098
10099     if (! cmailMsgLoaded) {
10100         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10101         return;
10102     }
10103
10104     if (nCmailGames == nCmailResults) {
10105         DisplayError(_("No unfinished games"), 0);
10106         return;
10107     }
10108
10109 #if CMAIL_PROHIBIT_REMAIL
10110     if (cmailMailedMove) {
10111         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);
10112         DisplayError(msg, 0);
10113         return;
10114     }
10115 #endif
10116
10117     if (! (cmailMailedMove || RegisterMove())) return;
10118     
10119     if (   cmailMailedMove
10120         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10121         sprintf(string, partCommandString,
10122                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10123         commandOutput = popen(string, "r");
10124
10125         if (commandOutput == NULL) {
10126             DisplayError(_("Failed to invoke cmail"), 0);
10127         } else {
10128             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10129                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10130             }
10131             if (nBuffers > 1) {
10132                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10133                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10134                 nBytes = MSG_SIZ - 1;
10135             } else {
10136                 (void) memcpy(msg, buffer, nBytes);
10137             }
10138             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10139
10140             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10141                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10142
10143                 archived = TRUE;
10144                 for (i = 0; i < nCmailGames; i ++) {
10145                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10146                         archived = FALSE;
10147                     }
10148                 }
10149                 if (   archived
10150                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10151                         != NULL)) {
10152                     sprintf(buffer, "%s/%s.%s.archive",
10153                             arcDir,
10154                             appData.cmailGameName,
10155                             gameInfo.date);
10156                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10157                     cmailMsgLoaded = FALSE;
10158                 }
10159             }
10160
10161             DisplayInformation(msg);
10162             pclose(commandOutput);
10163         }
10164     } else {
10165         if ((*cmailMsg) != '\0') {
10166             DisplayInformation(cmailMsg);
10167         }
10168     }
10169
10170     return;
10171 #endif /* !WIN32 */
10172 }
10173
10174 char *
10175 CmailMsg()
10176 {
10177 #if WIN32
10178     return NULL;
10179 #else
10180     int  prependComma = 0;
10181     char number[5];
10182     char string[MSG_SIZ];       /* Space for game-list */
10183     int  i;
10184     
10185     if (!cmailMsgLoaded) return "";
10186
10187     if (cmailMailedMove) {
10188         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10189     } else {
10190         /* Create a list of games left */
10191         sprintf(string, "[");
10192         for (i = 0; i < nCmailGames; i ++) {
10193             if (! (   cmailMoveRegistered[i]
10194                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10195                 if (prependComma) {
10196                     sprintf(number, ",%d", i + 1);
10197                 } else {
10198                     sprintf(number, "%d", i + 1);
10199                     prependComma = 1;
10200                 }
10201                 
10202                 strcat(string, number);
10203             }
10204         }
10205         strcat(string, "]");
10206
10207         if (nCmailMovesRegistered + nCmailResults == 0) {
10208             switch (nCmailGames) {
10209               case 1:
10210                 sprintf(cmailMsg,
10211                         _("Still need to make move for game\n"));
10212                 break;
10213                 
10214               case 2:
10215                 sprintf(cmailMsg,
10216                         _("Still need to make moves for both games\n"));
10217                 break;
10218                 
10219               default:
10220                 sprintf(cmailMsg,
10221                         _("Still need to make moves for all %d games\n"),
10222                         nCmailGames);
10223                 break;
10224             }
10225         } else {
10226             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10227               case 1:
10228                 sprintf(cmailMsg,
10229                         _("Still need to make a move for game %s\n"),
10230                         string);
10231                 break;
10232                 
10233               case 0:
10234                 if (nCmailResults == nCmailGames) {
10235                     sprintf(cmailMsg, _("No unfinished games\n"));
10236                 } else {
10237                     sprintf(cmailMsg, _("Ready to send mail\n"));
10238                 }
10239                 break;
10240                 
10241               default:
10242                 sprintf(cmailMsg,
10243                         _("Still need to make moves for games %s\n"),
10244                         string);
10245             }
10246         }
10247     }
10248     return cmailMsg;
10249 #endif /* WIN32 */
10250 }
10251
10252 void
10253 ResetGameEvent()
10254 {
10255     if (gameMode == Training)
10256       SetTrainingModeOff();
10257
10258     Reset(TRUE, TRUE);
10259     cmailMsgLoaded = FALSE;
10260     if (appData.icsActive) {
10261       SendToICS(ics_prefix);
10262       SendToICS("refresh\n");
10263     }
10264 }
10265
10266 void
10267 ExitEvent(status)
10268      int status;
10269 {
10270     exiting++;
10271     if (exiting > 2) {
10272       /* Give up on clean exit */
10273       exit(status);
10274     }
10275     if (exiting > 1) {
10276       /* Keep trying for clean exit */
10277       return;
10278     }
10279
10280     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10281
10282     if (telnetISR != NULL) {
10283       RemoveInputSource(telnetISR);
10284     }
10285     if (icsPR != NoProc) {
10286       DestroyChildProcess(icsPR, TRUE);
10287     }
10288 #if 0
10289     /* Save game if resource set and not already saved by GameEnds() */
10290     if ((gameInfo.resultDetails == NULL || errorExitFlag )
10291                              && forwardMostMove > 0) {
10292       if (*appData.saveGameFile != NULLCHAR) {
10293         SaveGameToFile(appData.saveGameFile, TRUE);
10294       } else if (appData.autoSaveGames) {
10295         AutoSaveGame();
10296       }
10297       if (*appData.savePositionFile != NULLCHAR) {
10298         SavePositionToFile(appData.savePositionFile);
10299       }
10300     }
10301     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10302 #else
10303     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10304     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10305 #endif
10306     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10307     /* make sure this other one finishes before killing it!                  */
10308     if(endingGame) { int count = 0;
10309         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10310         while(endingGame && count++ < 10) DoSleep(1);
10311         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10312     }
10313
10314     /* Kill off chess programs */
10315     if (first.pr != NoProc) {
10316         ExitAnalyzeMode();
10317         
10318         DoSleep( appData.delayBeforeQuit );
10319         SendToProgram("quit\n", &first);
10320         DoSleep( appData.delayAfterQuit );
10321         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10322     }
10323     if (second.pr != NoProc) {
10324         DoSleep( appData.delayBeforeQuit );
10325         SendToProgram("quit\n", &second);
10326         DoSleep( appData.delayAfterQuit );
10327         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10328     }
10329     if (first.isr != NULL) {
10330         RemoveInputSource(first.isr);
10331     }
10332     if (second.isr != NULL) {
10333         RemoveInputSource(second.isr);
10334     }
10335
10336     ShutDownFrontEnd();
10337     exit(status);
10338 }
10339
10340 void
10341 PauseEvent()
10342 {
10343     if (appData.debugMode)
10344         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10345     if (pausing) {
10346         pausing = FALSE;
10347         ModeHighlight();
10348         if (gameMode == MachinePlaysWhite ||
10349             gameMode == MachinePlaysBlack) {
10350             StartClocks();
10351         } else {
10352             DisplayBothClocks();
10353         }
10354         if (gameMode == PlayFromGameFile) {
10355             if (appData.timeDelay >= 0) 
10356                 AutoPlayGameLoop();
10357         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10358             Reset(FALSE, TRUE);
10359             SendToICS(ics_prefix);
10360             SendToICS("refresh\n");
10361         } else if (currentMove < forwardMostMove) {
10362             ForwardInner(forwardMostMove);
10363         }
10364         pauseExamInvalid = FALSE;
10365     } else {
10366         switch (gameMode) {
10367           default:
10368             return;
10369           case IcsExamining:
10370             pauseExamForwardMostMove = forwardMostMove;
10371             pauseExamInvalid = FALSE;
10372             /* fall through */
10373           case IcsObserving:
10374           case IcsPlayingWhite:
10375           case IcsPlayingBlack:
10376             pausing = TRUE;
10377             ModeHighlight();
10378             return;
10379           case PlayFromGameFile:
10380             (void) StopLoadGameTimer();
10381             pausing = TRUE;
10382             ModeHighlight();
10383             break;
10384           case BeginningOfGame:
10385             if (appData.icsActive) return;
10386             /* else fall through */
10387           case MachinePlaysWhite:
10388           case MachinePlaysBlack:
10389           case TwoMachinesPlay:
10390             if (forwardMostMove == 0)
10391               return;           /* don't pause if no one has moved */
10392             if ((gameMode == MachinePlaysWhite &&
10393                  !WhiteOnMove(forwardMostMove)) ||
10394                 (gameMode == MachinePlaysBlack &&
10395                  WhiteOnMove(forwardMostMove))) {
10396                 StopClocks();
10397             }
10398             pausing = TRUE;
10399             ModeHighlight();
10400             break;
10401         }
10402     }
10403 }
10404
10405 void
10406 EditCommentEvent()
10407 {
10408     char title[MSG_SIZ];
10409
10410     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10411         strcpy(title, _("Edit comment"));
10412     } else {
10413         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10414                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10415                 parseList[currentMove - 1]);
10416     }
10417
10418     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10419 }
10420
10421
10422 void
10423 EditTagsEvent()
10424 {
10425     char *tags = PGNTags(&gameInfo);
10426     EditTagsPopUp(tags);
10427     free(tags);
10428 }
10429
10430 void
10431 AnalyzeModeEvent()
10432 {
10433     if (appData.noChessProgram || gameMode == AnalyzeMode)
10434       return;
10435
10436     if (gameMode != AnalyzeFile) {
10437         if (!appData.icsEngineAnalyze) {
10438                EditGameEvent();
10439                if (gameMode != EditGame) return;
10440         }
10441         ResurrectChessProgram();
10442         SendToProgram("analyze\n", &first);
10443         first.analyzing = TRUE;
10444         /*first.maybeThinking = TRUE;*/
10445         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10446         AnalysisPopUp(_("Analysis"),
10447                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10448     }
10449     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10450     pausing = FALSE;
10451     ModeHighlight();
10452     SetGameInfo();
10453
10454     StartAnalysisClock();
10455     GetTimeMark(&lastNodeCountTime);
10456     lastNodeCount = 0;
10457 }
10458
10459 void
10460 AnalyzeFileEvent()
10461 {
10462     if (appData.noChessProgram || gameMode == AnalyzeFile)
10463       return;
10464
10465     if (gameMode != AnalyzeMode) {
10466         EditGameEvent();
10467         if (gameMode != EditGame) return;
10468         ResurrectChessProgram();
10469         SendToProgram("analyze\n", &first);
10470         first.analyzing = TRUE;
10471         /*first.maybeThinking = TRUE;*/
10472         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10473         AnalysisPopUp(_("Analysis"),
10474                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10475     }
10476     gameMode = AnalyzeFile;
10477     pausing = FALSE;
10478     ModeHighlight();
10479     SetGameInfo();
10480
10481     StartAnalysisClock();
10482     GetTimeMark(&lastNodeCountTime);
10483     lastNodeCount = 0;
10484 }
10485
10486 void
10487 MachineWhiteEvent()
10488 {
10489     char buf[MSG_SIZ];
10490     char *bookHit = NULL;
10491
10492     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10493       return;
10494
10495
10496     if (gameMode == PlayFromGameFile || 
10497         gameMode == TwoMachinesPlay  || 
10498         gameMode == Training         || 
10499         gameMode == AnalyzeMode      || 
10500         gameMode == EndOfGame)
10501         EditGameEvent();
10502
10503     if (gameMode == EditPosition) 
10504         EditPositionDone();
10505
10506     if (!WhiteOnMove(currentMove)) {
10507         DisplayError(_("It is not White's turn"), 0);
10508         return;
10509     }
10510   
10511     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10512       ExitAnalyzeMode();
10513
10514     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10515         gameMode == AnalyzeFile)
10516         TruncateGame();
10517
10518     ResurrectChessProgram();    /* in case it isn't running */
10519     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10520         gameMode = MachinePlaysWhite;
10521         ResetClocks();
10522     } else
10523     gameMode = MachinePlaysWhite;
10524     pausing = FALSE;
10525     ModeHighlight();
10526     SetGameInfo();
10527     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10528     DisplayTitle(buf);
10529     if (first.sendName) {
10530       sprintf(buf, "name %s\n", gameInfo.black);
10531       SendToProgram(buf, &first);
10532     }
10533     if (first.sendTime) {
10534       if (first.useColors) {
10535         SendToProgram("black\n", &first); /*gnu kludge*/
10536       }
10537       SendTimeRemaining(&first, TRUE);
10538     }
10539     if (first.useColors) {
10540       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10541     }
10542     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10543     SetMachineThinkingEnables();
10544     first.maybeThinking = TRUE;
10545     StartClocks();
10546     firstMove = FALSE;
10547
10548     if (appData.autoFlipView && !flipView) {
10549       flipView = !flipView;
10550       DrawPosition(FALSE, NULL);
10551       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10552     }
10553
10554     if(bookHit) { // [HGM] book: simulate book reply
10555         static char bookMove[MSG_SIZ]; // a bit generous?
10556
10557         programStats.nodes = programStats.depth = programStats.time = 
10558         programStats.score = programStats.got_only_move = 0;
10559         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10560
10561         strcpy(bookMove, "move ");
10562         strcat(bookMove, bookHit);
10563         HandleMachineMove(bookMove, &first);
10564     }
10565 }
10566
10567 void
10568 MachineBlackEvent()
10569 {
10570     char buf[MSG_SIZ];
10571    char *bookHit = NULL;
10572
10573     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10574         return;
10575
10576
10577     if (gameMode == PlayFromGameFile || 
10578         gameMode == TwoMachinesPlay  || 
10579         gameMode == Training         || 
10580         gameMode == AnalyzeMode      || 
10581         gameMode == EndOfGame)
10582         EditGameEvent();
10583
10584     if (gameMode == EditPosition) 
10585         EditPositionDone();
10586
10587     if (WhiteOnMove(currentMove)) {
10588         DisplayError(_("It is not Black's turn"), 0);
10589         return;
10590     }
10591     
10592     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10593       ExitAnalyzeMode();
10594
10595     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10596         gameMode == AnalyzeFile)
10597         TruncateGame();
10598
10599     ResurrectChessProgram();    /* in case it isn't running */
10600     gameMode = MachinePlaysBlack;
10601     pausing = FALSE;
10602     ModeHighlight();
10603     SetGameInfo();
10604     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10605     DisplayTitle(buf);
10606     if (first.sendName) {
10607       sprintf(buf, "name %s\n", gameInfo.white);
10608       SendToProgram(buf, &first);
10609     }
10610     if (first.sendTime) {
10611       if (first.useColors) {
10612         SendToProgram("white\n", &first); /*gnu kludge*/
10613       }
10614       SendTimeRemaining(&first, FALSE);
10615     }
10616     if (first.useColors) {
10617       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10618     }
10619     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10620     SetMachineThinkingEnables();
10621     first.maybeThinking = TRUE;
10622     StartClocks();
10623
10624     if (appData.autoFlipView && flipView) {
10625       flipView = !flipView;
10626       DrawPosition(FALSE, NULL);
10627       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10628     }
10629     if(bookHit) { // [HGM] book: simulate book reply
10630         static char bookMove[MSG_SIZ]; // a bit generous?
10631
10632         programStats.nodes = programStats.depth = programStats.time = 
10633         programStats.score = programStats.got_only_move = 0;
10634         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10635
10636         strcpy(bookMove, "move ");
10637         strcat(bookMove, bookHit);
10638         HandleMachineMove(bookMove, &first);
10639     }
10640 }
10641
10642
10643 void
10644 DisplayTwoMachinesTitle()
10645 {
10646     char buf[MSG_SIZ];
10647     if (appData.matchGames > 0) {
10648         if (first.twoMachinesColor[0] == 'w') {
10649             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10650                     gameInfo.white, gameInfo.black,
10651                     first.matchWins, second.matchWins,
10652                     matchGame - 1 - (first.matchWins + second.matchWins));
10653         } else {
10654             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10655                     gameInfo.white, gameInfo.black,
10656                     second.matchWins, first.matchWins,
10657                     matchGame - 1 - (first.matchWins + second.matchWins));
10658         }
10659     } else {
10660         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10661     }
10662     DisplayTitle(buf);
10663 }
10664
10665 void
10666 TwoMachinesEvent P((void))
10667 {
10668     int i;
10669     char buf[MSG_SIZ];
10670     ChessProgramState *onmove;
10671     char *bookHit = NULL;
10672     
10673     if (appData.noChessProgram) return;
10674
10675     switch (gameMode) {
10676       case TwoMachinesPlay:
10677         return;
10678       case MachinePlaysWhite:
10679       case MachinePlaysBlack:
10680         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10681             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10682             return;
10683         }
10684         /* fall through */
10685       case BeginningOfGame:
10686       case PlayFromGameFile:
10687       case EndOfGame:
10688         EditGameEvent();
10689         if (gameMode != EditGame) return;
10690         break;
10691       case EditPosition:
10692         EditPositionDone();
10693         break;
10694       case AnalyzeMode:
10695       case AnalyzeFile:
10696         ExitAnalyzeMode();
10697         break;
10698       case EditGame:
10699       default:
10700         break;
10701     }
10702
10703     forwardMostMove = currentMove;
10704     ResurrectChessProgram();    /* in case first program isn't running */
10705
10706     if (second.pr == NULL) {
10707         StartChessProgram(&second);
10708         if (second.protocolVersion == 1) {
10709           TwoMachinesEventIfReady();
10710         } else {
10711           /* kludge: allow timeout for initial "feature" command */
10712           FreezeUI();
10713           DisplayMessage("", _("Starting second chess program"));
10714           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10715         }
10716         return;
10717     }
10718     DisplayMessage("", "");
10719     InitChessProgram(&second, FALSE);
10720     SendToProgram("force\n", &second);
10721     if (startedFromSetupPosition) {
10722         SendBoard(&second, backwardMostMove);
10723     if (appData.debugMode) {
10724         fprintf(debugFP, "Two Machines\n");
10725     }
10726     }
10727     for (i = backwardMostMove; i < forwardMostMove; i++) {
10728         SendMoveToProgram(i, &second);
10729     }
10730
10731     gameMode = TwoMachinesPlay;
10732     pausing = FALSE;
10733     ModeHighlight();
10734     SetGameInfo();
10735     DisplayTwoMachinesTitle();
10736     firstMove = TRUE;
10737     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10738         onmove = &first;
10739     } else {
10740         onmove = &second;
10741     }
10742
10743     SendToProgram(first.computerString, &first);
10744     if (first.sendName) {
10745       sprintf(buf, "name %s\n", second.tidy);
10746       SendToProgram(buf, &first);
10747     }
10748     SendToProgram(second.computerString, &second);
10749     if (second.sendName) {
10750       sprintf(buf, "name %s\n", first.tidy);
10751       SendToProgram(buf, &second);
10752     }
10753
10754     ResetClocks();
10755     if (!first.sendTime || !second.sendTime) {
10756         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10757         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10758     }
10759     if (onmove->sendTime) {
10760       if (onmove->useColors) {
10761         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10762       }
10763       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10764     }
10765     if (onmove->useColors) {
10766       SendToProgram(onmove->twoMachinesColor, onmove);
10767     }
10768     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10769 //    SendToProgram("go\n", onmove);
10770     onmove->maybeThinking = TRUE;
10771     SetMachineThinkingEnables();
10772
10773     StartClocks();
10774
10775     if(bookHit) { // [HGM] book: simulate book reply
10776         static char bookMove[MSG_SIZ]; // a bit generous?
10777
10778         programStats.nodes = programStats.depth = programStats.time = 
10779         programStats.score = programStats.got_only_move = 0;
10780         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10781
10782         strcpy(bookMove, "move ");
10783         strcat(bookMove, bookHit);
10784         HandleMachineMove(bookMove, &first);
10785     }
10786 }
10787
10788 void
10789 TrainingEvent()
10790 {
10791     if (gameMode == Training) {
10792       SetTrainingModeOff();
10793       gameMode = PlayFromGameFile;
10794       DisplayMessage("", _("Training mode off"));
10795     } else {
10796       gameMode = Training;
10797       animateTraining = appData.animate;
10798
10799       /* make sure we are not already at the end of the game */
10800       if (currentMove < forwardMostMove) {
10801         SetTrainingModeOn();
10802         DisplayMessage("", _("Training mode on"));
10803       } else {
10804         gameMode = PlayFromGameFile;
10805         DisplayError(_("Already at end of game"), 0);
10806       }
10807     }
10808     ModeHighlight();
10809 }
10810
10811 void
10812 IcsClientEvent()
10813 {
10814     if (!appData.icsActive) return;
10815     switch (gameMode) {
10816       case IcsPlayingWhite:
10817       case IcsPlayingBlack:
10818       case IcsObserving:
10819       case IcsIdle:
10820       case BeginningOfGame:
10821       case IcsExamining:
10822         return;
10823
10824       case EditGame:
10825         break;
10826
10827       case EditPosition:
10828         EditPositionDone();
10829         break;
10830
10831       case AnalyzeMode:
10832       case AnalyzeFile:
10833         ExitAnalyzeMode();
10834         break;
10835         
10836       default:
10837         EditGameEvent();
10838         break;
10839     }
10840
10841     gameMode = IcsIdle;
10842     ModeHighlight();
10843     return;
10844 }
10845
10846
10847 void
10848 EditGameEvent()
10849 {
10850     int i;
10851
10852     switch (gameMode) {
10853       case Training:
10854         SetTrainingModeOff();
10855         break;
10856       case MachinePlaysWhite:
10857       case MachinePlaysBlack:
10858       case BeginningOfGame:
10859         SendToProgram("force\n", &first);
10860         SetUserThinkingEnables();
10861         break;
10862       case PlayFromGameFile:
10863         (void) StopLoadGameTimer();
10864         if (gameFileFP != NULL) {
10865             gameFileFP = NULL;
10866         }
10867         break;
10868       case EditPosition:
10869         EditPositionDone();
10870         break;
10871       case AnalyzeMode:
10872       case AnalyzeFile:
10873         ExitAnalyzeMode();
10874         SendToProgram("force\n", &first);
10875         break;
10876       case TwoMachinesPlay:
10877         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10878         ResurrectChessProgram();
10879         SetUserThinkingEnables();
10880         break;
10881       case EndOfGame:
10882         ResurrectChessProgram();
10883         break;
10884       case IcsPlayingBlack:
10885       case IcsPlayingWhite:
10886         DisplayError(_("Warning: You are still playing a game"), 0);
10887         break;
10888       case IcsObserving:
10889         DisplayError(_("Warning: You are still observing a game"), 0);
10890         break;
10891       case IcsExamining:
10892         DisplayError(_("Warning: You are still examining a game"), 0);
10893         break;
10894       case IcsIdle:
10895         break;
10896       case EditGame:
10897       default:
10898         return;
10899     }
10900     
10901     pausing = FALSE;
10902     StopClocks();
10903     first.offeredDraw = second.offeredDraw = 0;
10904
10905     if (gameMode == PlayFromGameFile) {
10906         whiteTimeRemaining = timeRemaining[0][currentMove];
10907         blackTimeRemaining = timeRemaining[1][currentMove];
10908         DisplayTitle("");
10909     }
10910
10911     if (gameMode == MachinePlaysWhite ||
10912         gameMode == MachinePlaysBlack ||
10913         gameMode == TwoMachinesPlay ||
10914         gameMode == EndOfGame) {
10915         i = forwardMostMove;
10916         while (i > currentMove) {
10917             SendToProgram("undo\n", &first);
10918             i--;
10919         }
10920         whiteTimeRemaining = timeRemaining[0][currentMove];
10921         blackTimeRemaining = timeRemaining[1][currentMove];
10922         DisplayBothClocks();
10923         if (whiteFlag || blackFlag) {
10924             whiteFlag = blackFlag = 0;
10925         }
10926         DisplayTitle("");
10927     }           
10928     
10929     gameMode = EditGame;
10930     ModeHighlight();
10931     SetGameInfo();
10932 }
10933
10934
10935 void
10936 EditPositionEvent()
10937 {
10938     if (gameMode == EditPosition) {
10939         EditGameEvent();
10940         return;
10941     }
10942     
10943     EditGameEvent();
10944     if (gameMode != EditGame) return;
10945     
10946     gameMode = EditPosition;
10947     ModeHighlight();
10948     SetGameInfo();
10949     if (currentMove > 0)
10950       CopyBoard(boards[0], boards[currentMove]);
10951     
10952     blackPlaysFirst = !WhiteOnMove(currentMove);
10953     ResetClocks();
10954     currentMove = forwardMostMove = backwardMostMove = 0;
10955     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10956     DisplayMove(-1);
10957 }
10958
10959 void
10960 ExitAnalyzeMode()
10961 {
10962     /* [DM] icsEngineAnalyze - possible call from other functions */
10963     if (appData.icsEngineAnalyze) {
10964         appData.icsEngineAnalyze = FALSE;
10965
10966         DisplayMessage("",_("Close ICS engine analyze..."));
10967     }
10968     if (first.analysisSupport && first.analyzing) {
10969       SendToProgram("exit\n", &first);
10970       first.analyzing = FALSE;
10971     }
10972     AnalysisPopDown();
10973     thinkOutput[0] = NULLCHAR;
10974 }
10975
10976 void
10977 EditPositionDone()
10978 {
10979     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
10980
10981     startedFromSetupPosition = TRUE;
10982     InitChessProgram(&first, FALSE);
10983     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
10984     if(boards[0][0][BOARD_WIDTH>>1] == king) {
10985         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
10986         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
10987     } else castlingRights[0][2] = -1;
10988     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
10989         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
10990         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
10991     } else castlingRights[0][5] = -1;
10992     SendToProgram("force\n", &first);
10993     if (blackPlaysFirst) {
10994         strcpy(moveList[0], "");
10995         strcpy(parseList[0], "");
10996         currentMove = forwardMostMove = backwardMostMove = 1;
10997         CopyBoard(boards[1], boards[0]);
10998         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10999         { int i;
11000           epStatus[1] = epStatus[0];
11001           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11002         }
11003     } else {
11004         currentMove = forwardMostMove = backwardMostMove = 0;
11005     }
11006     SendBoard(&first, forwardMostMove);
11007     if (appData.debugMode) {
11008         fprintf(debugFP, "EditPosDone\n");
11009     }
11010     DisplayTitle("");
11011     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11012     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11013     gameMode = EditGame;
11014     ModeHighlight();
11015     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11016     ClearHighlights(); /* [AS] */
11017 }
11018
11019 /* Pause for `ms' milliseconds */
11020 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11021 void
11022 TimeDelay(ms)
11023      long ms;
11024 {
11025     TimeMark m1, m2;
11026
11027     GetTimeMark(&m1);
11028     do {
11029         GetTimeMark(&m2);
11030     } while (SubtractTimeMarks(&m2, &m1) < ms);
11031 }
11032
11033 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11034 void
11035 SendMultiLineToICS(buf)
11036      char *buf;
11037 {
11038     char temp[MSG_SIZ+1], *p;
11039     int len;
11040
11041     len = strlen(buf);
11042     if (len > MSG_SIZ)
11043       len = MSG_SIZ;
11044   
11045     strncpy(temp, buf, len);
11046     temp[len] = 0;
11047
11048     p = temp;
11049     while (*p) {
11050         if (*p == '\n' || *p == '\r')
11051           *p = ' ';
11052         ++p;
11053     }
11054
11055     strcat(temp, "\n");
11056     SendToICS(temp);
11057     SendToPlayer(temp, strlen(temp));
11058 }
11059
11060 void
11061 SetWhiteToPlayEvent()
11062 {
11063     if (gameMode == EditPosition) {
11064         blackPlaysFirst = FALSE;
11065         DisplayBothClocks();    /* works because currentMove is 0 */
11066     } else if (gameMode == IcsExamining) {
11067         SendToICS(ics_prefix);
11068         SendToICS("tomove white\n");
11069     }
11070 }
11071
11072 void
11073 SetBlackToPlayEvent()
11074 {
11075     if (gameMode == EditPosition) {
11076         blackPlaysFirst = TRUE;
11077         currentMove = 1;        /* kludge */
11078         DisplayBothClocks();
11079         currentMove = 0;
11080     } else if (gameMode == IcsExamining) {
11081         SendToICS(ics_prefix);
11082         SendToICS("tomove black\n");
11083     }
11084 }
11085
11086 void
11087 EditPositionMenuEvent(selection, x, y)
11088      ChessSquare selection;
11089      int x, y;
11090 {
11091     char buf[MSG_SIZ];
11092     ChessSquare piece = boards[0][y][x];
11093
11094     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11095
11096     switch (selection) {
11097       case ClearBoard:
11098         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11099             SendToICS(ics_prefix);
11100             SendToICS("bsetup clear\n");
11101         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11102             SendToICS(ics_prefix);
11103             SendToICS("clearboard\n");
11104         } else {
11105             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11106                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11107                 for (y = 0; y < BOARD_HEIGHT; y++) {
11108                     if (gameMode == IcsExamining) {
11109                         if (boards[currentMove][y][x] != EmptySquare) {
11110                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11111                                     AAA + x, ONE + y);
11112                             SendToICS(buf);
11113                         }
11114                     } else {
11115                         boards[0][y][x] = p;
11116                     }
11117                 }
11118             }
11119         }
11120         if (gameMode == EditPosition) {
11121             DrawPosition(FALSE, boards[0]);
11122         }
11123         break;
11124
11125       case WhitePlay:
11126         SetWhiteToPlayEvent();
11127         break;
11128
11129       case BlackPlay:
11130         SetBlackToPlayEvent();
11131         break;
11132
11133       case EmptySquare:
11134         if (gameMode == IcsExamining) {
11135             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11136             SendToICS(buf);
11137         } else {
11138             boards[0][y][x] = EmptySquare;
11139             DrawPosition(FALSE, boards[0]);
11140         }
11141         break;
11142
11143       case PromotePiece:
11144         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11145            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11146             selection = (ChessSquare) (PROMOTED piece);
11147         } else if(piece == EmptySquare) selection = WhiteSilver;
11148         else selection = (ChessSquare)((int)piece - 1);
11149         goto defaultlabel;
11150
11151       case DemotePiece:
11152         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11153            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11154             selection = (ChessSquare) (DEMOTED piece);
11155         } else if(piece == EmptySquare) selection = BlackSilver;
11156         else selection = (ChessSquare)((int)piece + 1);       
11157         goto defaultlabel;
11158
11159       case WhiteQueen:
11160       case BlackQueen:
11161         if(gameInfo.variant == VariantShatranj ||
11162            gameInfo.variant == VariantXiangqi  ||
11163            gameInfo.variant == VariantCourier    )
11164             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11165         goto defaultlabel;
11166
11167       case WhiteKing:
11168       case BlackKing:
11169         if(gameInfo.variant == VariantXiangqi)
11170             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11171         if(gameInfo.variant == VariantKnightmate)
11172             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11173       default:
11174         defaultlabel:
11175         if (gameMode == IcsExamining) {
11176             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11177                     PieceToChar(selection), AAA + x, ONE + y);
11178             SendToICS(buf);
11179         } else {
11180             boards[0][y][x] = selection;
11181             DrawPosition(FALSE, boards[0]);
11182         }
11183         break;
11184     }
11185 }
11186
11187
11188 void
11189 DropMenuEvent(selection, x, y)
11190      ChessSquare selection;
11191      int x, y;
11192 {
11193     ChessMove moveType;
11194
11195     switch (gameMode) {
11196       case IcsPlayingWhite:
11197       case MachinePlaysBlack:
11198         if (!WhiteOnMove(currentMove)) {
11199             DisplayMoveError(_("It is Black's turn"));
11200             return;
11201         }
11202         moveType = WhiteDrop;
11203         break;
11204       case IcsPlayingBlack:
11205       case MachinePlaysWhite:
11206         if (WhiteOnMove(currentMove)) {
11207             DisplayMoveError(_("It is White's turn"));
11208             return;
11209         }
11210         moveType = BlackDrop;
11211         break;
11212       case EditGame:
11213         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11214         break;
11215       default:
11216         return;
11217     }
11218
11219     if (moveType == BlackDrop && selection < BlackPawn) {
11220       selection = (ChessSquare) ((int) selection
11221                                  + (int) BlackPawn - (int) WhitePawn);
11222     }
11223     if (boards[currentMove][y][x] != EmptySquare) {
11224         DisplayMoveError(_("That square is occupied"));
11225         return;
11226     }
11227
11228     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11229 }
11230
11231 void
11232 AcceptEvent()
11233 {
11234     /* Accept a pending offer of any kind from opponent */
11235     
11236     if (appData.icsActive) {
11237         SendToICS(ics_prefix);
11238         SendToICS("accept\n");
11239     } else if (cmailMsgLoaded) {
11240         if (currentMove == cmailOldMove &&
11241             commentList[cmailOldMove] != NULL &&
11242             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11243                    "Black offers a draw" : "White offers a draw")) {
11244             TruncateGame();
11245             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11246             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11247         } else {
11248             DisplayError(_("There is no pending offer on this move"), 0);
11249             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11250         }
11251     } else {
11252         /* Not used for offers from chess program */
11253     }
11254 }
11255
11256 void
11257 DeclineEvent()
11258 {
11259     /* Decline a pending offer of any kind from opponent */
11260     
11261     if (appData.icsActive) {
11262         SendToICS(ics_prefix);
11263         SendToICS("decline\n");
11264     } else if (cmailMsgLoaded) {
11265         if (currentMove == cmailOldMove &&
11266             commentList[cmailOldMove] != NULL &&
11267             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11268                    "Black offers a draw" : "White offers a draw")) {
11269 #ifdef NOTDEF
11270             AppendComment(cmailOldMove, "Draw declined");
11271             DisplayComment(cmailOldMove - 1, "Draw declined");
11272 #endif /*NOTDEF*/
11273         } else {
11274             DisplayError(_("There is no pending offer on this move"), 0);
11275         }
11276     } else {
11277         /* Not used for offers from chess program */
11278     }
11279 }
11280
11281 void
11282 RematchEvent()
11283 {
11284     /* Issue ICS rematch command */
11285     if (appData.icsActive) {
11286         SendToICS(ics_prefix);
11287         SendToICS("rematch\n");
11288     }
11289 }
11290
11291 void
11292 CallFlagEvent()
11293 {
11294     /* Call your opponent's flag (claim a win on time) */
11295     if (appData.icsActive) {
11296         SendToICS(ics_prefix);
11297         SendToICS("flag\n");
11298     } else {
11299         switch (gameMode) {
11300           default:
11301             return;
11302           case MachinePlaysWhite:
11303             if (whiteFlag) {
11304                 if (blackFlag)
11305                   GameEnds(GameIsDrawn, "Both players ran out of time",
11306                            GE_PLAYER);
11307                 else
11308                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11309             } else {
11310                 DisplayError(_("Your opponent is not out of time"), 0);
11311             }
11312             break;
11313           case MachinePlaysBlack:
11314             if (blackFlag) {
11315                 if (whiteFlag)
11316                   GameEnds(GameIsDrawn, "Both players ran out of time",
11317                            GE_PLAYER);
11318                 else
11319                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11320             } else {
11321                 DisplayError(_("Your opponent is not out of time"), 0);
11322             }
11323             break;
11324         }
11325     }
11326 }
11327
11328 void
11329 DrawEvent()
11330 {
11331     /* Offer draw or accept pending draw offer from opponent */
11332     
11333     if (appData.icsActive) {
11334         /* Note: tournament rules require draw offers to be
11335            made after you make your move but before you punch
11336            your clock.  Currently ICS doesn't let you do that;
11337            instead, you immediately punch your clock after making
11338            a move, but you can offer a draw at any time. */
11339         
11340         SendToICS(ics_prefix);
11341         SendToICS("draw\n");
11342     } else if (cmailMsgLoaded) {
11343         if (currentMove == cmailOldMove &&
11344             commentList[cmailOldMove] != NULL &&
11345             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11346                    "Black offers a draw" : "White offers a draw")) {
11347             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11348             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11349         } else if (currentMove == cmailOldMove + 1) {
11350             char *offer = WhiteOnMove(cmailOldMove) ?
11351               "White offers a draw" : "Black offers a draw";
11352             AppendComment(currentMove, offer);
11353             DisplayComment(currentMove - 1, offer);
11354             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11355         } else {
11356             DisplayError(_("You must make your move before offering a draw"), 0);
11357             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11358         }
11359     } else if (first.offeredDraw) {
11360         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11361     } else {
11362         if (first.sendDrawOffers) {
11363             SendToProgram("draw\n", &first);
11364             userOfferedDraw = TRUE;
11365         }
11366     }
11367 }
11368
11369 void
11370 AdjournEvent()
11371 {
11372     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11373     
11374     if (appData.icsActive) {
11375         SendToICS(ics_prefix);
11376         SendToICS("adjourn\n");
11377     } else {
11378         /* Currently GNU Chess doesn't offer or accept Adjourns */
11379     }
11380 }
11381
11382
11383 void
11384 AbortEvent()
11385 {
11386     /* Offer Abort or accept pending Abort offer from opponent */
11387     
11388     if (appData.icsActive) {
11389         SendToICS(ics_prefix);
11390         SendToICS("abort\n");
11391     } else {
11392         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11393     }
11394 }
11395
11396 void
11397 ResignEvent()
11398 {
11399     /* Resign.  You can do this even if it's not your turn. */
11400     
11401     if (appData.icsActive) {
11402         SendToICS(ics_prefix);
11403         SendToICS("resign\n");
11404     } else {
11405         switch (gameMode) {
11406           case MachinePlaysWhite:
11407             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11408             break;
11409           case MachinePlaysBlack:
11410             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11411             break;
11412           case EditGame:
11413             if (cmailMsgLoaded) {
11414                 TruncateGame();
11415                 if (WhiteOnMove(cmailOldMove)) {
11416                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11417                 } else {
11418                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11419                 }
11420                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11421             }
11422             break;
11423           default:
11424             break;
11425         }
11426     }
11427 }
11428
11429
11430 void
11431 StopObservingEvent()
11432 {
11433     /* Stop observing current games */
11434     SendToICS(ics_prefix);
11435     SendToICS("unobserve\n");
11436 }
11437
11438 void
11439 StopExaminingEvent()
11440 {
11441     /* Stop observing current game */
11442     SendToICS(ics_prefix);
11443     SendToICS("unexamine\n");
11444 }
11445
11446 void
11447 ForwardInner(target)
11448      int target;
11449 {
11450     int limit;
11451
11452     if (appData.debugMode)
11453         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11454                 target, currentMove, forwardMostMove);
11455
11456     if (gameMode == EditPosition)
11457       return;
11458
11459     if (gameMode == PlayFromGameFile && !pausing)
11460       PauseEvent();
11461     
11462     if (gameMode == IcsExamining && pausing)
11463       limit = pauseExamForwardMostMove;
11464     else
11465       limit = forwardMostMove;
11466     
11467     if (target > limit) target = limit;
11468
11469     if (target > 0 && moveList[target - 1][0]) {
11470         int fromX, fromY, toX, toY;
11471         toX = moveList[target - 1][2] - AAA;
11472         toY = moveList[target - 1][3] - ONE;
11473         if (moveList[target - 1][1] == '@') {
11474             if (appData.highlightLastMove) {
11475                 SetHighlights(-1, -1, toX, toY);
11476             }
11477         } else {
11478             fromX = moveList[target - 1][0] - AAA;
11479             fromY = moveList[target - 1][1] - ONE;
11480             if (target == currentMove + 1) {
11481                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11482             }
11483             if (appData.highlightLastMove) {
11484                 SetHighlights(fromX, fromY, toX, toY);
11485             }
11486         }
11487     }
11488     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11489         gameMode == Training || gameMode == PlayFromGameFile || 
11490         gameMode == AnalyzeFile) {
11491         while (currentMove < target) {
11492             SendMoveToProgram(currentMove++, &first);
11493         }
11494     } else {
11495         currentMove = target;
11496     }
11497     
11498     if (gameMode == EditGame || gameMode == EndOfGame) {
11499         whiteTimeRemaining = timeRemaining[0][currentMove];
11500         blackTimeRemaining = timeRemaining[1][currentMove];
11501     }
11502     DisplayBothClocks();
11503     DisplayMove(currentMove - 1);
11504     DrawPosition(FALSE, boards[currentMove]);
11505     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11506     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11507         DisplayComment(currentMove - 1, commentList[currentMove]);
11508     }
11509 }
11510
11511
11512 void
11513 ForwardEvent()
11514 {
11515     if (gameMode == IcsExamining && !pausing) {
11516         SendToICS(ics_prefix);
11517         SendToICS("forward\n");
11518     } else {
11519         ForwardInner(currentMove + 1);
11520     }
11521 }
11522
11523 void
11524 ToEndEvent()
11525 {
11526     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11527         /* to optimze, we temporarily turn off analysis mode while we feed
11528          * the remaining moves to the engine. Otherwise we get analysis output
11529          * after each move.
11530          */ 
11531         if (first.analysisSupport) {
11532           SendToProgram("exit\nforce\n", &first);
11533           first.analyzing = FALSE;
11534         }
11535     }
11536         
11537     if (gameMode == IcsExamining && !pausing) {
11538         SendToICS(ics_prefix);
11539         SendToICS("forward 999999\n");
11540     } else {
11541         ForwardInner(forwardMostMove);
11542     }
11543
11544     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11545         /* we have fed all the moves, so reactivate analysis mode */
11546         SendToProgram("analyze\n", &first);
11547         first.analyzing = TRUE;
11548         /*first.maybeThinking = TRUE;*/
11549         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11550     }
11551 }
11552
11553 void
11554 BackwardInner(target)
11555      int target;
11556 {
11557     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11558
11559     if (appData.debugMode)
11560         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11561                 target, currentMove, forwardMostMove);
11562
11563     if (gameMode == EditPosition) return;
11564     if (currentMove <= backwardMostMove) {
11565         ClearHighlights();
11566         DrawPosition(full_redraw, boards[currentMove]);
11567         return;
11568     }
11569     if (gameMode == PlayFromGameFile && !pausing)
11570       PauseEvent();
11571     
11572     if (moveList[target][0]) {
11573         int fromX, fromY, toX, toY;
11574         toX = moveList[target][2] - AAA;
11575         toY = moveList[target][3] - ONE;
11576         if (moveList[target][1] == '@') {
11577             if (appData.highlightLastMove) {
11578                 SetHighlights(-1, -1, toX, toY);
11579             }
11580         } else {
11581             fromX = moveList[target][0] - AAA;
11582             fromY = moveList[target][1] - ONE;
11583             if (target == currentMove - 1) {
11584                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11585             }
11586             if (appData.highlightLastMove) {
11587                 SetHighlights(fromX, fromY, toX, toY);
11588             }
11589         }
11590     }
11591     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11592         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11593         while (currentMove > target) {
11594             SendToProgram("undo\n", &first);
11595             currentMove--;
11596         }
11597     } else {
11598         currentMove = target;
11599     }
11600     
11601     if (gameMode == EditGame || gameMode == EndOfGame) {
11602         whiteTimeRemaining = timeRemaining[0][currentMove];
11603         blackTimeRemaining = timeRemaining[1][currentMove];
11604     }
11605     DisplayBothClocks();
11606     DisplayMove(currentMove - 1);
11607     DrawPosition(full_redraw, boards[currentMove]);
11608     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11609     // [HGM] PV info: routine tests if comment empty
11610     DisplayComment(currentMove - 1, commentList[currentMove]);
11611 }
11612
11613 void
11614 BackwardEvent()
11615 {
11616     if (gameMode == IcsExamining && !pausing) {
11617         SendToICS(ics_prefix);
11618         SendToICS("backward\n");
11619     } else {
11620         BackwardInner(currentMove - 1);
11621     }
11622 }
11623
11624 void
11625 ToStartEvent()
11626 {
11627     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11628         /* to optimze, we temporarily turn off analysis mode while we undo
11629          * all the moves. Otherwise we get analysis output after each undo.
11630          */ 
11631         if (first.analysisSupport) {
11632           SendToProgram("exit\nforce\n", &first);
11633           first.analyzing = FALSE;
11634         }
11635     }
11636
11637     if (gameMode == IcsExamining && !pausing) {
11638         SendToICS(ics_prefix);
11639         SendToICS("backward 999999\n");
11640     } else {
11641         BackwardInner(backwardMostMove);
11642     }
11643
11644     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11645         /* we have fed all the moves, so reactivate analysis mode */
11646         SendToProgram("analyze\n", &first);
11647         first.analyzing = TRUE;
11648         /*first.maybeThinking = TRUE;*/
11649         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11650     }
11651 }
11652
11653 void
11654 ToNrEvent(int to)
11655 {
11656   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11657   if (to >= forwardMostMove) to = forwardMostMove;
11658   if (to <= backwardMostMove) to = backwardMostMove;
11659   if (to < currentMove) {
11660     BackwardInner(to);
11661   } else {
11662     ForwardInner(to);
11663   }
11664 }
11665
11666 void
11667 RevertEvent()
11668 {
11669     if (gameMode != IcsExamining) {
11670         DisplayError(_("You are not examining a game"), 0);
11671         return;
11672     }
11673     if (pausing) {
11674         DisplayError(_("You can't revert while pausing"), 0);
11675         return;
11676     }
11677     SendToICS(ics_prefix);
11678     SendToICS("revert\n");
11679 }
11680
11681 void
11682 RetractMoveEvent()
11683 {
11684     switch (gameMode) {
11685       case MachinePlaysWhite:
11686       case MachinePlaysBlack:
11687         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11688             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11689             return;
11690         }
11691         if (forwardMostMove < 2) return;
11692         currentMove = forwardMostMove = forwardMostMove - 2;
11693         whiteTimeRemaining = timeRemaining[0][currentMove];
11694         blackTimeRemaining = timeRemaining[1][currentMove];
11695         DisplayBothClocks();
11696         DisplayMove(currentMove - 1);
11697         ClearHighlights();/*!! could figure this out*/
11698         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11699         SendToProgram("remove\n", &first);
11700         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11701         break;
11702
11703       case BeginningOfGame:
11704       default:
11705         break;
11706
11707       case IcsPlayingWhite:
11708       case IcsPlayingBlack:
11709         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11710             SendToICS(ics_prefix);
11711             SendToICS("takeback 2\n");
11712         } else {
11713             SendToICS(ics_prefix);
11714             SendToICS("takeback 1\n");
11715         }
11716         break;
11717     }
11718 }
11719
11720 void
11721 MoveNowEvent()
11722 {
11723     ChessProgramState *cps;
11724
11725     switch (gameMode) {
11726       case MachinePlaysWhite:
11727         if (!WhiteOnMove(forwardMostMove)) {
11728             DisplayError(_("It is your turn"), 0);
11729             return;
11730         }
11731         cps = &first;
11732         break;
11733       case MachinePlaysBlack:
11734         if (WhiteOnMove(forwardMostMove)) {
11735             DisplayError(_("It is your turn"), 0);
11736             return;
11737         }
11738         cps = &first;
11739         break;
11740       case TwoMachinesPlay:
11741         if (WhiteOnMove(forwardMostMove) ==
11742             (first.twoMachinesColor[0] == 'w')) {
11743             cps = &first;
11744         } else {
11745             cps = &second;
11746         }
11747         break;
11748       case BeginningOfGame:
11749       default:
11750         return;
11751     }
11752     SendToProgram("?\n", cps);
11753 }
11754
11755 void
11756 TruncateGameEvent()
11757 {
11758     EditGameEvent();
11759     if (gameMode != EditGame) return;
11760     TruncateGame();
11761 }
11762
11763 void
11764 TruncateGame()
11765 {
11766     if (forwardMostMove > currentMove) {
11767         if (gameInfo.resultDetails != NULL) {
11768             free(gameInfo.resultDetails);
11769             gameInfo.resultDetails = NULL;
11770             gameInfo.result = GameUnfinished;
11771         }
11772         forwardMostMove = currentMove;
11773         HistorySet(parseList, backwardMostMove, forwardMostMove,
11774                    currentMove-1);
11775     }
11776 }
11777
11778 void
11779 HintEvent()
11780 {
11781     if (appData.noChessProgram) return;
11782     switch (gameMode) {
11783       case MachinePlaysWhite:
11784         if (WhiteOnMove(forwardMostMove)) {
11785             DisplayError(_("Wait until your turn"), 0);
11786             return;
11787         }
11788         break;
11789       case BeginningOfGame:
11790       case MachinePlaysBlack:
11791         if (!WhiteOnMove(forwardMostMove)) {
11792             DisplayError(_("Wait until your turn"), 0);
11793             return;
11794         }
11795         break;
11796       default:
11797         DisplayError(_("No hint available"), 0);
11798         return;
11799     }
11800     SendToProgram("hint\n", &first);
11801     hintRequested = TRUE;
11802 }
11803
11804 void
11805 BookEvent()
11806 {
11807     if (appData.noChessProgram) return;
11808     switch (gameMode) {
11809       case MachinePlaysWhite:
11810         if (WhiteOnMove(forwardMostMove)) {
11811             DisplayError(_("Wait until your turn"), 0);
11812             return;
11813         }
11814         break;
11815       case BeginningOfGame:
11816       case MachinePlaysBlack:
11817         if (!WhiteOnMove(forwardMostMove)) {
11818             DisplayError(_("Wait until your turn"), 0);
11819             return;
11820         }
11821         break;
11822       case EditPosition:
11823         EditPositionDone();
11824         break;
11825       case TwoMachinesPlay:
11826         return;
11827       default:
11828         break;
11829     }
11830     SendToProgram("bk\n", &first);
11831     bookOutput[0] = NULLCHAR;
11832     bookRequested = TRUE;
11833 }
11834
11835 void
11836 AboutGameEvent()
11837 {
11838     char *tags = PGNTags(&gameInfo);
11839     TagsPopUp(tags, CmailMsg());
11840     free(tags);
11841 }
11842
11843 /* end button procedures */
11844
11845 void
11846 PrintPosition(fp, move)
11847      FILE *fp;
11848      int move;
11849 {
11850     int i, j;
11851     
11852     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11853         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11854             char c = PieceToChar(boards[move][i][j]);
11855             fputc(c == 'x' ? '.' : c, fp);
11856             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11857         }
11858     }
11859     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11860       fprintf(fp, "white to play\n");
11861     else
11862       fprintf(fp, "black to play\n");
11863 }
11864
11865 void
11866 PrintOpponents(fp)
11867      FILE *fp;
11868 {
11869     if (gameInfo.white != NULL) {
11870         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11871     } else {
11872         fprintf(fp, "\n");
11873     }
11874 }
11875
11876 /* Find last component of program's own name, using some heuristics */
11877 void
11878 TidyProgramName(prog, host, buf)
11879      char *prog, *host, buf[MSG_SIZ];
11880 {
11881     char *p, *q;
11882     int local = (strcmp(host, "localhost") == 0);
11883     while (!local && (p = strchr(prog, ';')) != NULL) {
11884         p++;
11885         while (*p == ' ') p++;
11886         prog = p;
11887     }
11888     if (*prog == '"' || *prog == '\'') {
11889         q = strchr(prog + 1, *prog);
11890     } else {
11891         q = strchr(prog, ' ');
11892     }
11893     if (q == NULL) q = prog + strlen(prog);
11894     p = q;
11895     while (p >= prog && *p != '/' && *p != '\\') p--;
11896     p++;
11897     if(p == prog && *p == '"') p++;
11898     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11899     memcpy(buf, p, q - p);
11900     buf[q - p] = NULLCHAR;
11901     if (!local) {
11902         strcat(buf, "@");
11903         strcat(buf, host);
11904     }
11905 }
11906
11907 char *
11908 TimeControlTagValue()
11909 {
11910     char buf[MSG_SIZ];
11911     if (!appData.clockMode) {
11912         strcpy(buf, "-");
11913     } else if (movesPerSession > 0) {
11914         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11915     } else if (timeIncrement == 0) {
11916         sprintf(buf, "%ld", timeControl/1000);
11917     } else {
11918         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11919     }
11920     return StrSave(buf);
11921 }
11922
11923 void
11924 SetGameInfo()
11925 {
11926     /* This routine is used only for certain modes */
11927     VariantClass v = gameInfo.variant;
11928     ClearGameInfo(&gameInfo);
11929     gameInfo.variant = v;
11930
11931     switch (gameMode) {
11932       case MachinePlaysWhite:
11933         gameInfo.event = StrSave( appData.pgnEventHeader );
11934         gameInfo.site = StrSave(HostName());
11935         gameInfo.date = PGNDate();
11936         gameInfo.round = StrSave("-");
11937         gameInfo.white = StrSave(first.tidy);
11938         gameInfo.black = StrSave(UserName());
11939         gameInfo.timeControl = TimeControlTagValue();
11940         break;
11941
11942       case MachinePlaysBlack:
11943         gameInfo.event = StrSave( appData.pgnEventHeader );
11944         gameInfo.site = StrSave(HostName());
11945         gameInfo.date = PGNDate();
11946         gameInfo.round = StrSave("-");
11947         gameInfo.white = StrSave(UserName());
11948         gameInfo.black = StrSave(first.tidy);
11949         gameInfo.timeControl = TimeControlTagValue();
11950         break;
11951
11952       case TwoMachinesPlay:
11953         gameInfo.event = StrSave( appData.pgnEventHeader );
11954         gameInfo.site = StrSave(HostName());
11955         gameInfo.date = PGNDate();
11956         if (matchGame > 0) {
11957             char buf[MSG_SIZ];
11958             sprintf(buf, "%d", matchGame);
11959             gameInfo.round = StrSave(buf);
11960         } else {
11961             gameInfo.round = StrSave("-");
11962         }
11963         if (first.twoMachinesColor[0] == 'w') {
11964             gameInfo.white = StrSave(first.tidy);
11965             gameInfo.black = StrSave(second.tidy);
11966         } else {
11967             gameInfo.white = StrSave(second.tidy);
11968             gameInfo.black = StrSave(first.tidy);
11969         }
11970         gameInfo.timeControl = TimeControlTagValue();
11971         break;
11972
11973       case EditGame:
11974         gameInfo.event = StrSave("Edited game");
11975         gameInfo.site = StrSave(HostName());
11976         gameInfo.date = PGNDate();
11977         gameInfo.round = StrSave("-");
11978         gameInfo.white = StrSave("-");
11979         gameInfo.black = StrSave("-");
11980         break;
11981
11982       case EditPosition:
11983         gameInfo.event = StrSave("Edited position");
11984         gameInfo.site = StrSave(HostName());
11985         gameInfo.date = PGNDate();
11986         gameInfo.round = StrSave("-");
11987         gameInfo.white = StrSave("-");
11988         gameInfo.black = StrSave("-");
11989         break;
11990
11991       case IcsPlayingWhite:
11992       case IcsPlayingBlack:
11993       case IcsObserving:
11994       case IcsExamining:
11995         break;
11996
11997       case PlayFromGameFile:
11998         gameInfo.event = StrSave("Game from non-PGN file");
11999         gameInfo.site = StrSave(HostName());
12000         gameInfo.date = PGNDate();
12001         gameInfo.round = StrSave("-");
12002         gameInfo.white = StrSave("?");
12003         gameInfo.black = StrSave("?");
12004         break;
12005
12006       default:
12007         break;
12008     }
12009 }
12010
12011 void
12012 ReplaceComment(index, text)
12013      int index;
12014      char *text;
12015 {
12016     int len;
12017
12018     while (*text == '\n') text++;
12019     len = strlen(text);
12020     while (len > 0 && text[len - 1] == '\n') len--;
12021
12022     if (commentList[index] != NULL)
12023       free(commentList[index]);
12024
12025     if (len == 0) {
12026         commentList[index] = NULL;
12027         return;
12028     }
12029     commentList[index] = (char *) malloc(len + 2);
12030     strncpy(commentList[index], text, len);
12031     commentList[index][len] = '\n';
12032     commentList[index][len + 1] = NULLCHAR;
12033 }
12034
12035 void
12036 CrushCRs(text)
12037      char *text;
12038 {
12039   char *p = text;
12040   char *q = text;
12041   char ch;
12042
12043   do {
12044     ch = *p++;
12045     if (ch == '\r') continue;
12046     *q++ = ch;
12047   } while (ch != '\0');
12048 }
12049
12050 void
12051 AppendComment(index, text)
12052      int index;
12053      char *text;
12054 {
12055     int oldlen, len;
12056     char *old;
12057
12058     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12059
12060     CrushCRs(text);
12061     while (*text == '\n') text++;
12062     len = strlen(text);
12063     while (len > 0 && text[len - 1] == '\n') len--;
12064
12065     if (len == 0) return;
12066
12067     if (commentList[index] != NULL) {
12068         old = commentList[index];
12069         oldlen = strlen(old);
12070         commentList[index] = (char *) malloc(oldlen + len + 2);
12071         strcpy(commentList[index], old);
12072         free(old);
12073         strncpy(&commentList[index][oldlen], text, len);
12074         commentList[index][oldlen + len] = '\n';
12075         commentList[index][oldlen + len + 1] = NULLCHAR;
12076     } else {
12077         commentList[index] = (char *) malloc(len + 2);
12078         strncpy(commentList[index], text, len);
12079         commentList[index][len] = '\n';
12080         commentList[index][len + 1] = NULLCHAR;
12081     }
12082 }
12083
12084 static char * FindStr( char * text, char * sub_text )
12085 {
12086     char * result = strstr( text, sub_text );
12087
12088     if( result != NULL ) {
12089         result += strlen( sub_text );
12090     }
12091
12092     return result;
12093 }
12094
12095 /* [AS] Try to extract PV info from PGN comment */
12096 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12097 char *GetInfoFromComment( int index, char * text )
12098 {
12099     char * sep = text;
12100
12101     if( text != NULL && index > 0 ) {
12102         int score = 0;
12103         int depth = 0;
12104         int time = -1, sec = 0, deci;
12105         char * s_eval = FindStr( text, "[%eval " );
12106         char * s_emt = FindStr( text, "[%emt " );
12107
12108         if( s_eval != NULL || s_emt != NULL ) {
12109             /* New style */
12110             char delim;
12111
12112             if( s_eval != NULL ) {
12113                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12114                     return text;
12115                 }
12116
12117                 if( delim != ']' ) {
12118                     return text;
12119                 }
12120             }
12121
12122             if( s_emt != NULL ) {
12123             }
12124         }
12125         else {
12126             /* We expect something like: [+|-]nnn.nn/dd */
12127             int score_lo = 0;
12128
12129             sep = strchr( text, '/' );
12130             if( sep == NULL || sep < (text+4) ) {
12131                 return text;
12132             }
12133
12134             time = -1; sec = -1; deci = -1;
12135             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12136                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12137                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12138                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12139                 return text;
12140             }
12141
12142             if( score_lo < 0 || score_lo >= 100 ) {
12143                 return text;
12144             }
12145
12146             if(sec >= 0) time = 600*time + 10*sec; else
12147             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12148
12149             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12150
12151             /* [HGM] PV time: now locate end of PV info */
12152             while( *++sep >= '0' && *sep <= '9'); // strip depth
12153             if(time >= 0)
12154             while( *++sep >= '0' && *sep <= '9'); // strip time
12155             if(sec >= 0)
12156             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12157             if(deci >= 0)
12158             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12159             while(*sep == ' ') sep++;
12160         }
12161
12162         if( depth <= 0 ) {
12163             return text;
12164         }
12165
12166         if( time < 0 ) {
12167             time = -1;
12168         }
12169
12170         pvInfoList[index-1].depth = depth;
12171         pvInfoList[index-1].score = score;
12172         pvInfoList[index-1].time  = 10*time; // centi-sec
12173     }
12174     return sep;
12175 }
12176
12177 void
12178 SendToProgram(message, cps)
12179      char *message;
12180      ChessProgramState *cps;
12181 {
12182     int count, outCount, error;
12183     char buf[MSG_SIZ];
12184
12185     if (cps->pr == NULL) return;
12186     Attention(cps);
12187     
12188     if (appData.debugMode) {
12189         TimeMark now;
12190         GetTimeMark(&now);
12191         fprintf(debugFP, "%ld >%-6s: %s", 
12192                 SubtractTimeMarks(&now, &programStartTime),
12193                 cps->which, message);
12194     }
12195     
12196     count = strlen(message);
12197     outCount = OutputToProcess(cps->pr, message, count, &error);
12198     if (outCount < count && !exiting 
12199                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12200         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12201         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12202             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12203                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12204                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12205             } else {
12206                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12207             }
12208             gameInfo.resultDetails = buf;
12209         }
12210         DisplayFatalError(buf, error, 1);
12211     }
12212 }
12213
12214 void
12215 ReceiveFromProgram(isr, closure, message, count, error)
12216      InputSourceRef isr;
12217      VOIDSTAR closure;
12218      char *message;
12219      int count;
12220      int error;
12221 {
12222     char *end_str;
12223     char buf[MSG_SIZ];
12224     ChessProgramState *cps = (ChessProgramState *)closure;
12225
12226     if (isr != cps->isr) return; /* Killed intentionally */
12227     if (count <= 0) {
12228         if (count == 0) {
12229             sprintf(buf,
12230                     _("Error: %s chess program (%s) exited unexpectedly"),
12231                     cps->which, cps->program);
12232         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12233                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12234                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12235                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12236                 } else {
12237                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12238                 }
12239                 gameInfo.resultDetails = buf;
12240             }
12241             RemoveInputSource(cps->isr);
12242             DisplayFatalError(buf, 0, 1);
12243         } else {
12244             sprintf(buf,
12245                     _("Error reading from %s chess program (%s)"),
12246                     cps->which, cps->program);
12247             RemoveInputSource(cps->isr);
12248
12249             /* [AS] Program is misbehaving badly... kill it */
12250             if( count == -2 ) {
12251                 DestroyChildProcess( cps->pr, 9 );
12252                 cps->pr = NoProc;
12253             }
12254
12255             DisplayFatalError(buf, error, 1);
12256         }
12257         return;
12258     }
12259     
12260     if ((end_str = strchr(message, '\r')) != NULL)
12261       *end_str = NULLCHAR;
12262     if ((end_str = strchr(message, '\n')) != NULL)
12263       *end_str = NULLCHAR;
12264     
12265     if (appData.debugMode) {
12266         TimeMark now; int print = 1;
12267         char *quote = ""; char c; int i;
12268
12269         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12270                 char start = message[0];
12271                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12272                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12273                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12274                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12275                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12276                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12277                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12278                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12279                         { quote = "# "; print = (appData.engineComments == 2); }
12280                 message[0] = start; // restore original message
12281         }
12282         if(print) {
12283                 GetTimeMark(&now);
12284                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12285                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12286                         quote,
12287                         message);
12288         }
12289     }
12290
12291     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12292     if (appData.icsEngineAnalyze) {
12293         if (strstr(message, "whisper") != NULL ||
12294              strstr(message, "kibitz") != NULL || 
12295             strstr(message, "tellics") != NULL) return;
12296     }
12297
12298     HandleMachineMove(message, cps);
12299 }
12300
12301
12302 void
12303 SendTimeControl(cps, mps, tc, inc, sd, st)
12304      ChessProgramState *cps;
12305      int mps, inc, sd, st;
12306      long tc;
12307 {
12308     char buf[MSG_SIZ];
12309     int seconds;
12310
12311     if( timeControl_2 > 0 ) {
12312         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12313             tc = timeControl_2;
12314         }
12315     }
12316     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12317     inc /= cps->timeOdds;
12318     st  /= cps->timeOdds;
12319
12320     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12321
12322     if (st > 0) {
12323       /* Set exact time per move, normally using st command */
12324       if (cps->stKludge) {
12325         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12326         seconds = st % 60;
12327         if (seconds == 0) {
12328           sprintf(buf, "level 1 %d\n", st/60);
12329         } else {
12330           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12331         }
12332       } else {
12333         sprintf(buf, "st %d\n", st);
12334       }
12335     } else {
12336       /* Set conventional or incremental time control, using level command */
12337       if (seconds == 0) {
12338         /* Note old gnuchess bug -- minutes:seconds used to not work.
12339            Fixed in later versions, but still avoid :seconds
12340            when seconds is 0. */
12341         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12342       } else {
12343         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12344                 seconds, inc/1000);
12345       }
12346     }
12347     SendToProgram(buf, cps);
12348
12349     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12350     /* Orthogonally, limit search to given depth */
12351     if (sd > 0) {
12352       if (cps->sdKludge) {
12353         sprintf(buf, "depth\n%d\n", sd);
12354       } else {
12355         sprintf(buf, "sd %d\n", sd);
12356       }
12357       SendToProgram(buf, cps);
12358     }
12359
12360     if(cps->nps > 0) { /* [HGM] nps */
12361         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12362         else {
12363                 sprintf(buf, "nps %d\n", cps->nps);
12364               SendToProgram(buf, cps);
12365         }
12366     }
12367 }
12368
12369 ChessProgramState *WhitePlayer()
12370 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12371 {
12372     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12373        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12374         return &second;
12375     return &first;
12376 }
12377
12378 void
12379 SendTimeRemaining(cps, machineWhite)
12380      ChessProgramState *cps;
12381      int /*boolean*/ machineWhite;
12382 {
12383     char message[MSG_SIZ];
12384     long time, otime;
12385
12386     /* Note: this routine must be called when the clocks are stopped
12387        or when they have *just* been set or switched; otherwise
12388        it will be off by the time since the current tick started.
12389     */
12390     if (machineWhite) {
12391         time = whiteTimeRemaining / 10;
12392         otime = blackTimeRemaining / 10;
12393     } else {
12394         time = blackTimeRemaining / 10;
12395         otime = whiteTimeRemaining / 10;
12396     }
12397     /* [HGM] translate opponent's time by time-odds factor */
12398     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12399     if (appData.debugMode) {
12400         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12401     }
12402
12403     if (time <= 0) time = 1;
12404     if (otime <= 0) otime = 1;
12405     
12406     sprintf(message, "time %ld\n", time);
12407     SendToProgram(message, cps);
12408
12409     sprintf(message, "otim %ld\n", otime);
12410     SendToProgram(message, cps);
12411 }
12412
12413 int
12414 BoolFeature(p, name, loc, cps)
12415      char **p;
12416      char *name;
12417      int *loc;
12418      ChessProgramState *cps;
12419 {
12420   char buf[MSG_SIZ];
12421   int len = strlen(name);
12422   int val;
12423   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12424     (*p) += len + 1;
12425     sscanf(*p, "%d", &val);
12426     *loc = (val != 0);
12427     while (**p && **p != ' ') (*p)++;
12428     sprintf(buf, "accepted %s\n", name);
12429     SendToProgram(buf, cps);
12430     return TRUE;
12431   }
12432   return FALSE;
12433 }
12434
12435 int
12436 IntFeature(p, name, loc, cps)
12437      char **p;
12438      char *name;
12439      int *loc;
12440      ChessProgramState *cps;
12441 {
12442   char buf[MSG_SIZ];
12443   int len = strlen(name);
12444   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12445     (*p) += len + 1;
12446     sscanf(*p, "%d", loc);
12447     while (**p && **p != ' ') (*p)++;
12448     sprintf(buf, "accepted %s\n", name);
12449     SendToProgram(buf, cps);
12450     return TRUE;
12451   }
12452   return FALSE;
12453 }
12454
12455 int
12456 StringFeature(p, name, loc, cps)
12457      char **p;
12458      char *name;
12459      char loc[];
12460      ChessProgramState *cps;
12461 {
12462   char buf[MSG_SIZ];
12463   int len = strlen(name);
12464   if (strncmp((*p), name, len) == 0
12465       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12466     (*p) += len + 2;
12467     sscanf(*p, "%[^\"]", loc);
12468     while (**p && **p != '\"') (*p)++;
12469     if (**p == '\"') (*p)++;
12470     sprintf(buf, "accepted %s\n", name);
12471     SendToProgram(buf, cps);
12472     return TRUE;
12473   }
12474   return FALSE;
12475 }
12476
12477 int 
12478 ParseOption(Option *opt, ChessProgramState *cps)
12479 // [HGM] options: process the string that defines an engine option, and determine
12480 // name, type, default value, and allowed value range
12481 {
12482         char *p, *q, buf[MSG_SIZ];
12483         int n, min = (-1)<<31, max = 1<<31, def;
12484
12485         if(p = strstr(opt->name, " -spin ")) {
12486             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12487             if(max < min) max = min; // enforce consistency
12488             if(def < min) def = min;
12489             if(def > max) def = max;
12490             opt->value = def;
12491             opt->min = min;
12492             opt->max = max;
12493             opt->type = Spin;
12494         } else if((p = strstr(opt->name, " -slider "))) {
12495             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12496             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12497             if(max < min) max = min; // enforce consistency
12498             if(def < min) def = min;
12499             if(def > max) def = max;
12500             opt->value = def;
12501             opt->min = min;
12502             opt->max = max;
12503             opt->type = Spin; // Slider;
12504         } else if((p = strstr(opt->name, " -string "))) {
12505             opt->textValue = p+9;
12506             opt->type = TextBox;
12507         } else if((p = strstr(opt->name, " -file "))) {
12508             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12509             opt->textValue = p+7;
12510             opt->type = TextBox; // FileName;
12511         } else if((p = strstr(opt->name, " -path "))) {
12512             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12513             opt->textValue = p+7;
12514             opt->type = TextBox; // PathName;
12515         } else if(p = strstr(opt->name, " -check ")) {
12516             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12517             opt->value = (def != 0);
12518             opt->type = CheckBox;
12519         } else if(p = strstr(opt->name, " -combo ")) {
12520             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12521             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12522             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12523             opt->value = n = 0;
12524             while(q = StrStr(q, " /// ")) {
12525                 n++; *q = 0;    // count choices, and null-terminate each of them
12526                 q += 5;
12527                 if(*q == '*') { // remember default, which is marked with * prefix
12528                     q++;
12529                     opt->value = n;
12530                 }
12531                 cps->comboList[cps->comboCnt++] = q;
12532             }
12533             cps->comboList[cps->comboCnt++] = NULL;
12534             opt->max = n + 1;
12535             opt->type = ComboBox;
12536         } else if(p = strstr(opt->name, " -button")) {
12537             opt->type = Button;
12538         } else if(p = strstr(opt->name, " -save")) {
12539             opt->type = SaveButton;
12540         } else return FALSE;
12541         *p = 0; // terminate option name
12542         // now look if the command-line options define a setting for this engine option.
12543         if(cps->optionSettings && cps->optionSettings[0])
12544             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12545         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12546                 sprintf(buf, "option %s", p);
12547                 if(p = strstr(buf, ",")) *p = 0;
12548                 strcat(buf, "\n");
12549                 SendToProgram(buf, cps);
12550         }
12551         return TRUE;
12552 }
12553
12554 void
12555 FeatureDone(cps, val)
12556      ChessProgramState* cps;
12557      int val;
12558 {
12559   DelayedEventCallback cb = GetDelayedEvent();
12560   if ((cb == InitBackEnd3 && cps == &first) ||
12561       (cb == TwoMachinesEventIfReady && cps == &second)) {
12562     CancelDelayedEvent();
12563     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12564   }
12565   cps->initDone = val;
12566 }
12567
12568 /* Parse feature command from engine */
12569 void
12570 ParseFeatures(args, cps)
12571      char* args;
12572      ChessProgramState *cps;  
12573 {
12574   char *p = args;
12575   char *q;
12576   int val;
12577   char buf[MSG_SIZ];
12578
12579   for (;;) {
12580     while (*p == ' ') p++;
12581     if (*p == NULLCHAR) return;
12582
12583     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12584     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12585     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12586     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12587     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12588     if (BoolFeature(&p, "reuse", &val, cps)) {
12589       /* Engine can disable reuse, but can't enable it if user said no */
12590       if (!val) cps->reuse = FALSE;
12591       continue;
12592     }
12593     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12594     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12595       if (gameMode == TwoMachinesPlay) {
12596         DisplayTwoMachinesTitle();
12597       } else {
12598         DisplayTitle("");
12599       }
12600       continue;
12601     }
12602     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12603     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12604     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12605     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12606     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12607     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12608     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12609     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12610     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12611     if (IntFeature(&p, "done", &val, cps)) {
12612       FeatureDone(cps, val);
12613       continue;
12614     }
12615     /* Added by Tord: */
12616     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12617     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12618     /* End of additions by Tord */
12619
12620     /* [HGM] added features: */
12621     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12622     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12623     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12624     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12625     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12626     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12627     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12628         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12629             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12630             SendToProgram(buf, cps);
12631             continue;
12632         }
12633         if(cps->nrOptions >= MAX_OPTIONS) {
12634             cps->nrOptions--;
12635             sprintf(buf, "%s engine has too many options\n", cps->which);
12636             DisplayError(buf, 0);
12637         }
12638         continue;
12639     }
12640     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12641     /* End of additions by HGM */
12642
12643     /* unknown feature: complain and skip */
12644     q = p;
12645     while (*q && *q != '=') q++;
12646     sprintf(buf, "rejected %.*s\n", q-p, p);
12647     SendToProgram(buf, cps);
12648     p = q;
12649     if (*p == '=') {
12650       p++;
12651       if (*p == '\"') {
12652         p++;
12653         while (*p && *p != '\"') p++;
12654         if (*p == '\"') p++;
12655       } else {
12656         while (*p && *p != ' ') p++;
12657       }
12658     }
12659   }
12660
12661 }
12662
12663 void
12664 PeriodicUpdatesEvent(newState)
12665      int newState;
12666 {
12667     if (newState == appData.periodicUpdates)
12668       return;
12669
12670     appData.periodicUpdates=newState;
12671
12672     /* Display type changes, so update it now */
12673     DisplayAnalysis();
12674
12675     /* Get the ball rolling again... */
12676     if (newState) {
12677         AnalysisPeriodicEvent(1);
12678         StartAnalysisClock();
12679     }
12680 }
12681
12682 void
12683 PonderNextMoveEvent(newState)
12684      int newState;
12685 {
12686     if (newState == appData.ponderNextMove) return;
12687     if (gameMode == EditPosition) EditPositionDone();
12688     if (newState) {
12689         SendToProgram("hard\n", &first);
12690         if (gameMode == TwoMachinesPlay) {
12691             SendToProgram("hard\n", &second);
12692         }
12693     } else {
12694         SendToProgram("easy\n", &first);
12695         thinkOutput[0] = NULLCHAR;
12696         if (gameMode == TwoMachinesPlay) {
12697             SendToProgram("easy\n", &second);
12698         }
12699     }
12700     appData.ponderNextMove = newState;
12701 }
12702
12703 void
12704 NewSettingEvent(option, command, value)
12705      char *command;
12706      int option, value;
12707 {
12708     char buf[MSG_SIZ];
12709
12710     if (gameMode == EditPosition) EditPositionDone();
12711     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12712     SendToProgram(buf, &first);
12713     if (gameMode == TwoMachinesPlay) {
12714         SendToProgram(buf, &second);
12715     }
12716 }
12717
12718 void
12719 ShowThinkingEvent()
12720 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12721 {
12722     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12723     int newState = appData.showThinking
12724         // [HGM] thinking: other features now need thinking output as well
12725         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12726     
12727     if (oldState == newState) return;
12728     oldState = newState;
12729     if (gameMode == EditPosition) EditPositionDone();
12730     if (oldState) {
12731         SendToProgram("post\n", &first);
12732         if (gameMode == TwoMachinesPlay) {
12733             SendToProgram("post\n", &second);
12734         }
12735     } else {
12736         SendToProgram("nopost\n", &first);
12737         thinkOutput[0] = NULLCHAR;
12738         if (gameMode == TwoMachinesPlay) {
12739             SendToProgram("nopost\n", &second);
12740         }
12741     }
12742 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12743 }
12744
12745 void
12746 AskQuestionEvent(title, question, replyPrefix, which)
12747      char *title; char *question; char *replyPrefix; char *which;
12748 {
12749   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12750   if (pr == NoProc) return;
12751   AskQuestion(title, question, replyPrefix, pr);
12752 }
12753
12754 void
12755 DisplayMove(moveNumber)
12756      int moveNumber;
12757 {
12758     char message[MSG_SIZ];
12759     char res[MSG_SIZ];
12760     char cpThinkOutput[MSG_SIZ];
12761
12762     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12763     
12764     if (moveNumber == forwardMostMove - 1 || 
12765         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12766
12767         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12768
12769         if (strchr(cpThinkOutput, '\n')) {
12770             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12771         }
12772     } else {
12773         *cpThinkOutput = NULLCHAR;
12774     }
12775
12776     /* [AS] Hide thinking from human user */
12777     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12778         *cpThinkOutput = NULLCHAR;
12779         if( thinkOutput[0] != NULLCHAR ) {
12780             int i;
12781
12782             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12783                 cpThinkOutput[i] = '.';
12784             }
12785             cpThinkOutput[i] = NULLCHAR;
12786             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12787         }
12788     }
12789
12790     if (moveNumber == forwardMostMove - 1 &&
12791         gameInfo.resultDetails != NULL) {
12792         if (gameInfo.resultDetails[0] == NULLCHAR) {
12793             sprintf(res, " %s", PGNResult(gameInfo.result));
12794         } else {
12795             sprintf(res, " {%s} %s",
12796                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12797         }
12798     } else {
12799         res[0] = NULLCHAR;
12800     }
12801
12802     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12803         DisplayMessage(res, cpThinkOutput);
12804     } else {
12805         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12806                 WhiteOnMove(moveNumber) ? " " : ".. ",
12807                 parseList[moveNumber], res);
12808         DisplayMessage(message, cpThinkOutput);
12809     }
12810 }
12811
12812 void
12813 DisplayAnalysisText(text)
12814      char *text;
12815 {
12816     char buf[MSG_SIZ];
12817
12818     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile 
12819                || appData.icsEngineAnalyze) {
12820         sprintf(buf, "Analysis (%s)", first.tidy);
12821         AnalysisPopUp(buf, text);
12822     }
12823 }
12824
12825 static int
12826 only_one_move(str)
12827      char *str;
12828 {
12829     while (*str && isspace(*str)) ++str;
12830     while (*str && !isspace(*str)) ++str;
12831     if (!*str) return 1;
12832     while (*str && isspace(*str)) ++str;
12833     if (!*str) return 1;
12834     return 0;
12835 }
12836
12837 void
12838 DisplayAnalysis()
12839 {
12840     char buf[MSG_SIZ];
12841     char lst[MSG_SIZ / 2];
12842     double nps;
12843     static char *xtra[] = { "", " (--)", " (++)" };
12844     int h, m, s, cs;
12845   
12846     if (programStats.time == 0) {
12847         programStats.time = 1;
12848     }
12849   
12850     if (programStats.got_only_move) {
12851         safeStrCpy(buf, programStats.movelist, sizeof(buf));
12852     } else {
12853         safeStrCpy( lst, programStats.movelist, sizeof(lst));
12854
12855         nps = (u64ToDouble(programStats.nodes) /
12856              ((double)programStats.time /100.0));
12857
12858         cs = programStats.time % 100;
12859         s = programStats.time / 100;
12860         h = (s / (60*60));
12861         s = s - h*60*60;
12862         m = (s/60);
12863         s = s - m*60;
12864
12865         if (programStats.moves_left > 0 && appData.periodicUpdates) {
12866           if (programStats.move_name[0] != NULLCHAR) {
12867             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12868                     programStats.depth,
12869                     programStats.nr_moves-programStats.moves_left,
12870                     programStats.nr_moves, programStats.move_name,
12871                     ((float)programStats.score)/100.0, lst,
12872                     only_one_move(lst)?
12873                     xtra[programStats.got_fail] : "",
12874                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12875           } else {
12876             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12877                     programStats.depth,
12878                     programStats.nr_moves-programStats.moves_left,
12879                     programStats.nr_moves, ((float)programStats.score)/100.0,
12880                     lst,
12881                     only_one_move(lst)?
12882                     xtra[programStats.got_fail] : "",
12883                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12884           }
12885         } else {
12886             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12887                     programStats.depth,
12888                     ((float)programStats.score)/100.0,
12889                     lst,
12890                     only_one_move(lst)?
12891                     xtra[programStats.got_fail] : "",
12892                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12893         }
12894     }
12895     DisplayAnalysisText(buf);
12896 }
12897
12898 void
12899 DisplayComment(moveNumber, text)
12900      int moveNumber;
12901      char *text;
12902 {
12903     char title[MSG_SIZ];
12904     char buf[8000]; // comment can be long!
12905     int score, depth;
12906
12907     if( appData.autoDisplayComment ) {
12908         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12909             strcpy(title, "Comment");
12910         } else {
12911             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12912                     WhiteOnMove(moveNumber) ? " " : ".. ",
12913                     parseList[moveNumber]);
12914         }
12915         // [HGM] PV info: display PV info together with (or as) comment
12916         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12917             if(text == NULL) text = "";                                           
12918             score = pvInfoList[moveNumber].score;
12919             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12920                               depth, (pvInfoList[moveNumber].time+50)/100, text);
12921             text = buf;
12922         }
12923     } else title[0] = 0;
12924
12925     if (text != NULL)
12926         CommentPopUp(title, text);
12927 }
12928
12929 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12930  * might be busy thinking or pondering.  It can be omitted if your
12931  * gnuchess is configured to stop thinking immediately on any user
12932  * input.  However, that gnuchess feature depends on the FIONREAD
12933  * ioctl, which does not work properly on some flavors of Unix.
12934  */
12935 void
12936 Attention(cps)
12937      ChessProgramState *cps;
12938 {
12939 #if ATTENTION
12940     if (!cps->useSigint) return;
12941     if (appData.noChessProgram || (cps->pr == NoProc)) return;
12942     switch (gameMode) {
12943       case MachinePlaysWhite:
12944       case MachinePlaysBlack:
12945       case TwoMachinesPlay:
12946       case IcsPlayingWhite:
12947       case IcsPlayingBlack:
12948       case AnalyzeMode:
12949       case AnalyzeFile:
12950         /* Skip if we know it isn't thinking */
12951         if (!cps->maybeThinking) return;
12952         if (appData.debugMode)
12953           fprintf(debugFP, "Interrupting %s\n", cps->which);
12954         InterruptChildProcess(cps->pr);
12955         cps->maybeThinking = FALSE;
12956         break;
12957       default:
12958         break;
12959     }
12960 #endif /*ATTENTION*/
12961 }
12962
12963 int
12964 CheckFlags()
12965 {
12966     if (whiteTimeRemaining <= 0) {
12967         if (!whiteFlag) {
12968             whiteFlag = TRUE;
12969             if (appData.icsActive) {
12970                 if (appData.autoCallFlag &&
12971                     gameMode == IcsPlayingBlack && !blackFlag) {
12972                   SendToICS(ics_prefix);
12973                   SendToICS("flag\n");
12974                 }
12975             } else {
12976                 if (blackFlag) {
12977                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12978                 } else {
12979                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12980                     if (appData.autoCallFlag) {
12981                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12982                         return TRUE;
12983                     }
12984                 }
12985             }
12986         }
12987     }
12988     if (blackTimeRemaining <= 0) {
12989         if (!blackFlag) {
12990             blackFlag = TRUE;
12991             if (appData.icsActive) {
12992                 if (appData.autoCallFlag &&
12993                     gameMode == IcsPlayingWhite && !whiteFlag) {
12994                   SendToICS(ics_prefix);
12995                   SendToICS("flag\n");
12996                 }
12997             } else {
12998                 if (whiteFlag) {
12999                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13000                 } else {
13001                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13002                     if (appData.autoCallFlag) {
13003                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13004                         return TRUE;
13005                     }
13006                 }
13007             }
13008         }
13009     }
13010     return FALSE;
13011 }
13012
13013 void
13014 CheckTimeControl()
13015 {
13016     if (!appData.clockMode || appData.icsActive ||
13017         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13018
13019     /*
13020      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13021      */
13022     if ( !WhiteOnMove(forwardMostMove) )
13023         /* White made time control */
13024         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13025         /* [HGM] time odds: correct new time quota for time odds! */
13026                                             / WhitePlayer()->timeOdds;
13027       else
13028         /* Black made time control */
13029         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13030                                             / WhitePlayer()->other->timeOdds;
13031 }
13032
13033 void
13034 DisplayBothClocks()
13035 {
13036     int wom = gameMode == EditPosition ?
13037       !blackPlaysFirst : WhiteOnMove(currentMove);
13038     DisplayWhiteClock(whiteTimeRemaining, wom);
13039     DisplayBlackClock(blackTimeRemaining, !wom);
13040 }
13041
13042
13043 /* Timekeeping seems to be a portability nightmare.  I think everyone
13044    has ftime(), but I'm really not sure, so I'm including some ifdefs
13045    to use other calls if you don't.  Clocks will be less accurate if
13046    you have neither ftime nor gettimeofday.
13047 */
13048
13049 /* VS 2008 requires the #include outside of the function */
13050 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13051 #include <sys/timeb.h>
13052 #endif
13053
13054 /* Get the current time as a TimeMark */
13055 void
13056 GetTimeMark(tm)
13057      TimeMark *tm;
13058 {
13059 #if HAVE_GETTIMEOFDAY
13060
13061     struct timeval timeVal;
13062     struct timezone timeZone;
13063
13064     gettimeofday(&timeVal, &timeZone);
13065     tm->sec = (long) timeVal.tv_sec; 
13066     tm->ms = (int) (timeVal.tv_usec / 1000L);
13067
13068 #else /*!HAVE_GETTIMEOFDAY*/
13069 #if HAVE_FTIME
13070
13071 // include <sys/timeb.h> / moved to just above start of function
13072     struct timeb timeB;
13073
13074     ftime(&timeB);
13075     tm->sec = (long) timeB.time;
13076     tm->ms = (int) timeB.millitm;
13077
13078 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13079     tm->sec = (long) time(NULL);
13080     tm->ms = 0;
13081 #endif
13082 #endif
13083 }
13084
13085 /* Return the difference in milliseconds between two
13086    time marks.  We assume the difference will fit in a long!
13087 */
13088 long
13089 SubtractTimeMarks(tm2, tm1)
13090      TimeMark *tm2, *tm1;
13091 {
13092     return 1000L*(tm2->sec - tm1->sec) +
13093            (long) (tm2->ms - tm1->ms);
13094 }
13095
13096
13097 /*
13098  * Code to manage the game clocks.
13099  *
13100  * In tournament play, black starts the clock and then white makes a move.
13101  * We give the human user a slight advantage if he is playing white---the
13102  * clocks don't run until he makes his first move, so it takes zero time.
13103  * Also, we don't account for network lag, so we could get out of sync
13104  * with GNU Chess's clock -- but then, referees are always right.  
13105  */
13106
13107 static TimeMark tickStartTM;
13108 static long intendedTickLength;
13109
13110 long
13111 NextTickLength(timeRemaining)
13112      long timeRemaining;
13113 {
13114     long nominalTickLength, nextTickLength;
13115
13116     if (timeRemaining > 0L && timeRemaining <= 10000L)
13117       nominalTickLength = 100L;
13118     else
13119       nominalTickLength = 1000L;
13120     nextTickLength = timeRemaining % nominalTickLength;
13121     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13122
13123     return nextTickLength;
13124 }
13125
13126 /* Adjust clock one minute up or down */
13127 void
13128 AdjustClock(Boolean which, int dir)
13129 {
13130     if(which) blackTimeRemaining += 60000*dir;
13131     else      whiteTimeRemaining += 60000*dir;
13132     DisplayBothClocks();
13133 }
13134
13135 /* Stop clocks and reset to a fresh time control */
13136 void
13137 ResetClocks() 
13138 {
13139     (void) StopClockTimer();
13140     if (appData.icsActive) {
13141         whiteTimeRemaining = blackTimeRemaining = 0;
13142     } else { /* [HGM] correct new time quote for time odds */
13143         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13144         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13145     }
13146     if (whiteFlag || blackFlag) {
13147         DisplayTitle("");
13148         whiteFlag = blackFlag = FALSE;
13149     }
13150     DisplayBothClocks();
13151 }
13152
13153 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13154
13155 /* Decrement running clock by amount of time that has passed */
13156 void
13157 DecrementClocks()
13158 {
13159     long timeRemaining;
13160     long lastTickLength, fudge;
13161     TimeMark now;
13162
13163     if (!appData.clockMode) return;
13164     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13165         
13166     GetTimeMark(&now);
13167
13168     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13169
13170     /* Fudge if we woke up a little too soon */
13171     fudge = intendedTickLength - lastTickLength;
13172     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13173
13174     if (WhiteOnMove(forwardMostMove)) {
13175         if(whiteNPS >= 0) lastTickLength = 0;
13176         timeRemaining = whiteTimeRemaining -= lastTickLength;
13177         DisplayWhiteClock(whiteTimeRemaining - fudge,
13178                           WhiteOnMove(currentMove));
13179     } else {
13180         if(blackNPS >= 0) lastTickLength = 0;
13181         timeRemaining = blackTimeRemaining -= lastTickLength;
13182         DisplayBlackClock(blackTimeRemaining - fudge,
13183                           !WhiteOnMove(currentMove));
13184     }
13185
13186     if (CheckFlags()) return;
13187         
13188     tickStartTM = now;
13189     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13190     StartClockTimer(intendedTickLength);
13191
13192     /* if the time remaining has fallen below the alarm threshold, sound the
13193      * alarm. if the alarm has sounded and (due to a takeback or time control
13194      * with increment) the time remaining has increased to a level above the
13195      * threshold, reset the alarm so it can sound again. 
13196      */
13197     
13198     if (appData.icsActive && appData.icsAlarm) {
13199
13200         /* make sure we are dealing with the user's clock */
13201         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13202                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13203            )) return;
13204
13205         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13206             alarmSounded = FALSE;
13207         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13208             PlayAlarmSound();
13209             alarmSounded = TRUE;
13210         }
13211     }
13212 }
13213
13214
13215 /* A player has just moved, so stop the previously running
13216    clock and (if in clock mode) start the other one.
13217    We redisplay both clocks in case we're in ICS mode, because
13218    ICS gives us an update to both clocks after every move.
13219    Note that this routine is called *after* forwardMostMove
13220    is updated, so the last fractional tick must be subtracted
13221    from the color that is *not* on move now.
13222 */
13223 void
13224 SwitchClocks()
13225 {
13226     long lastTickLength;
13227     TimeMark now;
13228     int flagged = FALSE;
13229
13230     GetTimeMark(&now);
13231
13232     if (StopClockTimer() && appData.clockMode) {
13233         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13234         if (WhiteOnMove(forwardMostMove)) {
13235             if(blackNPS >= 0) lastTickLength = 0;
13236             blackTimeRemaining -= lastTickLength;
13237            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13238 //         if(pvInfoList[forwardMostMove-1].time == -1)
13239                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13240                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13241         } else {
13242            if(whiteNPS >= 0) lastTickLength = 0;
13243            whiteTimeRemaining -= lastTickLength;
13244            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13245 //         if(pvInfoList[forwardMostMove-1].time == -1)
13246                  pvInfoList[forwardMostMove-1].time = 
13247                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13248         }
13249         flagged = CheckFlags();
13250     }
13251     CheckTimeControl();
13252
13253     if (flagged || !appData.clockMode) return;
13254
13255     switch (gameMode) {
13256       case MachinePlaysBlack:
13257       case MachinePlaysWhite:
13258       case BeginningOfGame:
13259         if (pausing) return;
13260         break;
13261
13262       case EditGame:
13263       case PlayFromGameFile:
13264       case IcsExamining:
13265         return;
13266
13267       default:
13268         break;
13269     }
13270
13271     tickStartTM = now;
13272     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13273       whiteTimeRemaining : blackTimeRemaining);
13274     StartClockTimer(intendedTickLength);
13275 }
13276         
13277
13278 /* Stop both clocks */
13279 void
13280 StopClocks()
13281 {       
13282     long lastTickLength;
13283     TimeMark now;
13284
13285     if (!StopClockTimer()) return;
13286     if (!appData.clockMode) return;
13287
13288     GetTimeMark(&now);
13289
13290     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13291     if (WhiteOnMove(forwardMostMove)) {
13292         if(whiteNPS >= 0) lastTickLength = 0;
13293         whiteTimeRemaining -= lastTickLength;
13294         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13295     } else {
13296         if(blackNPS >= 0) lastTickLength = 0;
13297         blackTimeRemaining -= lastTickLength;
13298         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13299     }
13300     CheckFlags();
13301 }
13302         
13303 /* Start clock of player on move.  Time may have been reset, so
13304    if clock is already running, stop and restart it. */
13305 void
13306 StartClocks()
13307 {
13308     (void) StopClockTimer(); /* in case it was running already */
13309     DisplayBothClocks();
13310     if (CheckFlags()) return;
13311
13312     if (!appData.clockMode) return;
13313     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13314
13315     GetTimeMark(&tickStartTM);
13316     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13317       whiteTimeRemaining : blackTimeRemaining);
13318
13319    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13320     whiteNPS = blackNPS = -1; 
13321     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13322        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13323         whiteNPS = first.nps;
13324     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13325        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13326         blackNPS = first.nps;
13327     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13328         whiteNPS = second.nps;
13329     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13330         blackNPS = second.nps;
13331     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13332
13333     StartClockTimer(intendedTickLength);
13334 }
13335
13336 char *
13337 TimeString(ms)
13338      long ms;
13339 {
13340     long second, minute, hour, day;
13341     char *sign = "";
13342     static char buf[32];
13343     
13344     if (ms > 0 && ms <= 9900) {
13345       /* convert milliseconds to tenths, rounding up */
13346       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13347
13348       sprintf(buf, " %03.1f ", tenths/10.0);
13349       return buf;
13350     }
13351
13352     /* convert milliseconds to seconds, rounding up */
13353     /* use floating point to avoid strangeness of integer division
13354        with negative dividends on many machines */
13355     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13356
13357     if (second < 0) {
13358         sign = "-";
13359         second = -second;
13360     }
13361     
13362     day = second / (60 * 60 * 24);
13363     second = second % (60 * 60 * 24);
13364     hour = second / (60 * 60);
13365     second = second % (60 * 60);
13366     minute = second / 60;
13367     second = second % 60;
13368     
13369     if (day > 0)
13370       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13371               sign, day, hour, minute, second);
13372     else if (hour > 0)
13373       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13374     else
13375       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13376     
13377     return buf;
13378 }
13379
13380
13381 /*
13382  * This is necessary because some C libraries aren't ANSI C compliant yet.
13383  */
13384 char *
13385 StrStr(string, match)
13386      char *string, *match;
13387 {
13388     int i, length;
13389     
13390     length = strlen(match);
13391     
13392     for (i = strlen(string) - length; i >= 0; i--, string++)
13393       if (!strncmp(match, string, length))
13394         return string;
13395     
13396     return NULL;
13397 }
13398
13399 char *
13400 StrCaseStr(string, match)
13401      char *string, *match;
13402 {
13403     int i, j, length;
13404     
13405     length = strlen(match);
13406     
13407     for (i = strlen(string) - length; i >= 0; i--, string++) {
13408         for (j = 0; j < length; j++) {
13409             if (ToLower(match[j]) != ToLower(string[j]))
13410               break;
13411         }
13412         if (j == length) return string;
13413     }
13414
13415     return NULL;
13416 }
13417
13418 #ifndef _amigados
13419 int
13420 StrCaseCmp(s1, s2)
13421      char *s1, *s2;
13422 {
13423     char c1, c2;
13424     
13425     for (;;) {
13426         c1 = ToLower(*s1++);
13427         c2 = ToLower(*s2++);
13428         if (c1 > c2) return 1;
13429         if (c1 < c2) return -1;
13430         if (c1 == NULLCHAR) return 0;
13431     }
13432 }
13433
13434
13435 int
13436 ToLower(c)
13437      int c;
13438 {
13439     return isupper(c) ? tolower(c) : c;
13440 }
13441
13442
13443 int
13444 ToUpper(c)
13445      int c;
13446 {
13447     return islower(c) ? toupper(c) : c;
13448 }
13449 #endif /* !_amigados    */
13450
13451 char *
13452 StrSave(s)
13453      char *s;
13454 {
13455     char *ret;
13456
13457     if ((ret = (char *) malloc(strlen(s) + 1))) {
13458         strcpy(ret, s);
13459     }
13460     return ret;
13461 }
13462
13463 char *
13464 StrSavePtr(s, savePtr)
13465      char *s, **savePtr;
13466 {
13467     if (*savePtr) {
13468         free(*savePtr);
13469     }
13470     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13471         strcpy(*savePtr, s);
13472     }
13473     return(*savePtr);
13474 }
13475
13476 char *
13477 PGNDate()
13478 {
13479     time_t clock;
13480     struct tm *tm;
13481     char buf[MSG_SIZ];
13482
13483     clock = time((time_t *)NULL);
13484     tm = localtime(&clock);
13485     sprintf(buf, "%04d.%02d.%02d",
13486             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13487     return StrSave(buf);
13488 }
13489
13490
13491 char *
13492 PositionToFEN(move, overrideCastling)
13493      int move;
13494      char *overrideCastling;
13495 {
13496     int i, j, fromX, fromY, toX, toY;
13497     int whiteToPlay;
13498     char buf[128];
13499     char *p, *q;
13500     int emptycount;
13501     ChessSquare piece;
13502
13503     whiteToPlay = (gameMode == EditPosition) ?
13504       !blackPlaysFirst : (move % 2 == 0);
13505     p = buf;
13506
13507     /* Piece placement data */
13508     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13509         emptycount = 0;
13510         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13511             if (boards[move][i][j] == EmptySquare) {
13512                 emptycount++;
13513             } else { ChessSquare piece = boards[move][i][j];
13514                 if (emptycount > 0) {
13515                     if(emptycount<10) /* [HGM] can be >= 10 */
13516                         *p++ = '0' + emptycount;
13517                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13518                     emptycount = 0;
13519                 }
13520                 if(PieceToChar(piece) == '+') {
13521                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13522                     *p++ = '+';
13523                     piece = (ChessSquare)(DEMOTED piece);
13524                 } 
13525                 *p++ = PieceToChar(piece);
13526                 if(p[-1] == '~') {
13527                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13528                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13529                     *p++ = '~';
13530                 }
13531             }
13532         }
13533         if (emptycount > 0) {
13534             if(emptycount<10) /* [HGM] can be >= 10 */
13535                 *p++ = '0' + emptycount;
13536             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13537             emptycount = 0;
13538         }
13539         *p++ = '/';
13540     }
13541     *(p - 1) = ' ';
13542
13543     /* [HGM] print Crazyhouse or Shogi holdings */
13544     if( gameInfo.holdingsWidth ) {
13545         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13546         q = p;
13547         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13548             piece = boards[move][i][BOARD_WIDTH-1];
13549             if( piece != EmptySquare )
13550               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13551                   *p++ = PieceToChar(piece);
13552         }
13553         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13554             piece = boards[move][BOARD_HEIGHT-i-1][0];
13555             if( piece != EmptySquare )
13556               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13557                   *p++ = PieceToChar(piece);
13558         }
13559
13560         if( q == p ) *p++ = '-';
13561         *p++ = ']';
13562         *p++ = ' ';
13563     }
13564
13565     /* Active color */
13566     *p++ = whiteToPlay ? 'w' : 'b';
13567     *p++ = ' ';
13568
13569   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13570     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13571   } else {
13572   if(nrCastlingRights) {
13573      q = p;
13574      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13575        /* [HGM] write directly from rights */
13576            if(castlingRights[move][2] >= 0 &&
13577               castlingRights[move][0] >= 0   )
13578                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13579            if(castlingRights[move][2] >= 0 &&
13580               castlingRights[move][1] >= 0   )
13581                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13582            if(castlingRights[move][5] >= 0 &&
13583               castlingRights[move][3] >= 0   )
13584                 *p++ = castlingRights[move][3] + AAA;
13585            if(castlingRights[move][5] >= 0 &&
13586               castlingRights[move][4] >= 0   )
13587                 *p++ = castlingRights[move][4] + AAA;
13588      } else {
13589
13590         /* [HGM] write true castling rights */
13591         if( nrCastlingRights == 6 ) {
13592             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13593                castlingRights[move][2] >= 0  ) *p++ = 'K';
13594             if(castlingRights[move][1] == BOARD_LEFT &&
13595                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13596             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13597                castlingRights[move][5] >= 0  ) *p++ = 'k';
13598             if(castlingRights[move][4] == BOARD_LEFT &&
13599                castlingRights[move][5] >= 0  ) *p++ = 'q';
13600         }
13601      }
13602      if (q == p) *p++ = '-'; /* No castling rights */
13603      *p++ = ' ';
13604   }
13605
13606   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13607      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13608     /* En passant target square */
13609     if (move > backwardMostMove) {
13610         fromX = moveList[move - 1][0] - AAA;
13611         fromY = moveList[move - 1][1] - ONE;
13612         toX = moveList[move - 1][2] - AAA;
13613         toY = moveList[move - 1][3] - ONE;
13614         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13615             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13616             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13617             fromX == toX) {
13618             /* 2-square pawn move just happened */
13619             *p++ = toX + AAA;
13620             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13621         } else {
13622             *p++ = '-';
13623         }
13624     } else {
13625         *p++ = '-';
13626     }
13627     *p++ = ' ';
13628   }
13629   }
13630
13631     /* [HGM] find reversible plies */
13632     {   int i = 0, j=move;
13633
13634         if (appData.debugMode) { int k;
13635             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13636             for(k=backwardMostMove; k<=forwardMostMove; k++)
13637                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13638
13639         }
13640
13641         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13642         if( j == backwardMostMove ) i += initialRulePlies;
13643         sprintf(p, "%d ", i);
13644         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13645     }
13646     /* Fullmove number */
13647     sprintf(p, "%d", (move / 2) + 1);
13648     
13649     return StrSave(buf);
13650 }
13651
13652 Boolean
13653 ParseFEN(board, blackPlaysFirst, fen)
13654     Board board;
13655      int *blackPlaysFirst;
13656      char *fen;
13657 {
13658     int i, j;
13659     char *p;
13660     int emptycount;
13661     ChessSquare piece;
13662
13663     p = fen;
13664
13665     /* [HGM] by default clear Crazyhouse holdings, if present */
13666     if(gameInfo.holdingsWidth) {
13667        for(i=0; i<BOARD_HEIGHT; i++) {
13668            board[i][0]             = EmptySquare; /* black holdings */
13669            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13670            board[i][1]             = (ChessSquare) 0; /* black counts */
13671            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13672        }
13673     }
13674
13675     /* Piece placement data */
13676     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13677         j = 0;
13678         for (;;) {
13679             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13680                 if (*p == '/') p++;
13681                 emptycount = gameInfo.boardWidth - j;
13682                 while (emptycount--)
13683                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13684                 break;
13685 #if(BOARD_SIZE >= 10)
13686             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13687                 p++; emptycount=10;
13688                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13689                 while (emptycount--)
13690                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13691 #endif
13692             } else if (isdigit(*p)) {
13693                 emptycount = *p++ - '0';
13694                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13695                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13696                 while (emptycount--)
13697                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13698             } else if (*p == '+' || isalpha(*p)) {
13699                 if (j >= gameInfo.boardWidth) return FALSE;
13700                 if(*p=='+') {
13701                     piece = CharToPiece(*++p);
13702                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13703                     piece = (ChessSquare) (PROMOTED piece ); p++;
13704                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13705                 } else piece = CharToPiece(*p++);
13706
13707                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13708                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13709                     piece = (ChessSquare) (PROMOTED piece);
13710                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13711                     p++;
13712                 }
13713                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13714             } else {
13715                 return FALSE;
13716             }
13717         }
13718     }
13719     while (*p == '/' || *p == ' ') p++;
13720
13721     /* [HGM] look for Crazyhouse holdings here */
13722     while(*p==' ') p++;
13723     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13724         if(*p == '[') p++;
13725         if(*p == '-' ) *p++; /* empty holdings */ else {
13726             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13727             /* if we would allow FEN reading to set board size, we would   */
13728             /* have to add holdings and shift the board read so far here   */
13729             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13730                 *p++;
13731                 if((int) piece >= (int) BlackPawn ) {
13732                     i = (int)piece - (int)BlackPawn;
13733                     i = PieceToNumber((ChessSquare)i);
13734                     if( i >= gameInfo.holdingsSize ) return FALSE;
13735                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13736                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13737                 } else {
13738                     i = (int)piece - (int)WhitePawn;
13739                     i = PieceToNumber((ChessSquare)i);
13740                     if( i >= gameInfo.holdingsSize ) return FALSE;
13741                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13742                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13743                 }
13744             }
13745         }
13746         if(*p == ']') *p++;
13747     }
13748
13749     while(*p == ' ') p++;
13750
13751     /* Active color */
13752     switch (*p++) {
13753       case 'w':
13754         *blackPlaysFirst = FALSE;
13755         break;
13756       case 'b': 
13757         *blackPlaysFirst = TRUE;
13758         break;
13759       default:
13760         return FALSE;
13761     }
13762
13763     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13764     /* return the extra info in global variiables             */
13765
13766     /* set defaults in case FEN is incomplete */
13767     FENepStatus = EP_UNKNOWN;
13768     for(i=0; i<nrCastlingRights; i++ ) {
13769         FENcastlingRights[i] =
13770             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13771     }   /* assume possible unless obviously impossible */
13772     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13773     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13774     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13775     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13776     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13777     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13778     FENrulePlies = 0;
13779
13780     while(*p==' ') p++;
13781     if(nrCastlingRights) {
13782       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13783           /* castling indicator present, so default becomes no castlings */
13784           for(i=0; i<nrCastlingRights; i++ ) {
13785                  FENcastlingRights[i] = -1;
13786           }
13787       }
13788       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13789              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13790              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13791              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13792         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13793
13794         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13795             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13796             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13797         }
13798         switch(c) {
13799           case'K':
13800               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13801               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13802               FENcastlingRights[2] = whiteKingFile;
13803               break;
13804           case'Q':
13805               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13806               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13807               FENcastlingRights[2] = whiteKingFile;
13808               break;
13809           case'k':
13810               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13811               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13812               FENcastlingRights[5] = blackKingFile;
13813               break;
13814           case'q':
13815               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13816               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13817               FENcastlingRights[5] = blackKingFile;
13818           case '-':
13819               break;
13820           default: /* FRC castlings */
13821               if(c >= 'a') { /* black rights */
13822                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13823                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13824                   if(i == BOARD_RGHT) break;
13825                   FENcastlingRights[5] = i;
13826                   c -= AAA;
13827                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13828                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13829                   if(c > i)
13830                       FENcastlingRights[3] = c;
13831                   else
13832                       FENcastlingRights[4] = c;
13833               } else { /* white rights */
13834                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13835                     if(board[0][i] == WhiteKing) break;
13836                   if(i == BOARD_RGHT) break;
13837                   FENcastlingRights[2] = i;
13838                   c -= AAA - 'a' + 'A';
13839                   if(board[0][c] >= WhiteKing) break;
13840                   if(c > i)
13841                       FENcastlingRights[0] = c;
13842                   else
13843                       FENcastlingRights[1] = c;
13844               }
13845         }
13846       }
13847     if (appData.debugMode) {
13848         fprintf(debugFP, "FEN castling rights:");
13849         for(i=0; i<nrCastlingRights; i++)
13850         fprintf(debugFP, " %d", FENcastlingRights[i]);
13851         fprintf(debugFP, "\n");
13852     }
13853
13854       while(*p==' ') p++;
13855     }
13856
13857     /* read e.p. field in games that know e.p. capture */
13858     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13859        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13860       if(*p=='-') {
13861         p++; FENepStatus = EP_NONE;
13862       } else {
13863          char c = *p++ - AAA;
13864
13865          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13866          if(*p >= '0' && *p <='9') *p++;
13867          FENepStatus = c;
13868       }
13869     }
13870
13871
13872     if(sscanf(p, "%d", &i) == 1) {
13873         FENrulePlies = i; /* 50-move ply counter */
13874         /* (The move number is still ignored)    */
13875     }
13876
13877     return TRUE;
13878 }
13879       
13880 void
13881 EditPositionPasteFEN(char *fen)
13882 {
13883   if (fen != NULL) {
13884     Board initial_position;
13885
13886     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13887       DisplayError(_("Bad FEN position in clipboard"), 0);
13888       return ;
13889     } else {
13890       int savedBlackPlaysFirst = blackPlaysFirst;
13891       EditPositionEvent();
13892       blackPlaysFirst = savedBlackPlaysFirst;
13893       CopyBoard(boards[0], initial_position);
13894           /* [HGM] copy FEN attributes as well */
13895           {   int i;
13896               initialRulePlies = FENrulePlies;
13897               epStatus[0] = FENepStatus;
13898               for( i=0; i<nrCastlingRights; i++ )
13899                   castlingRights[0][i] = FENcastlingRights[i];
13900           }
13901       EditPositionDone();
13902       DisplayBothClocks();
13903       DrawPosition(FALSE, boards[currentMove]);
13904     }
13905   }
13906 }