fixed autoconf problem with VERSION/PATCHLEVEL variables
[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.  Enhancements Copyright
6  * 1992-2001,2002,2003,2004,2005,2006,2007,2008,2009 Free Software
7  * Foundation, Inc.
8  *
9  * The following terms apply to Digital Equipment Corporation's copyright
10  * interest in XBoard:
11  * ------------------------------------------------------------------------
12  * All Rights Reserved
13  *
14  * Permission to use, copy, modify, and distribute this software and its
15  * documentation for any purpose and without fee is hereby granted,
16  * provided that the above copyright notice appear in all copies and that
17  * both that copyright notice and this permission notice appear in
18  * supporting documentation, and that the name of Digital not be
19  * used in advertising or publicity pertaining to distribution of the
20  * software without specific, written prior permission.
21  *
22  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
23  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
24  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
25  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
26  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
27  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
28  * SOFTWARE.
29  * ------------------------------------------------------------------------
30  *
31  * The following terms apply to the enhanced version of XBoard
32  * distributed by the Free Software Foundation:
33  * ------------------------------------------------------------------------
34  *
35  * GNU XBoard is free software: you can redistribute it and/or modify
36  * it under the terms of the GNU General Public License as published by
37  * the Free Software Foundation, either version 3 of the License, or (at
38  * your option) any later version.
39  *
40  * GNU XBoard is distributed in the hope that it will be useful, but
41  * WITHOUT ANY WARRANTY; without even the implied warranty of
42  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
43  * General Public License for more details.
44  *
45  * You should have received a copy of the GNU General Public License
46  * along with this program. If not, see http://www.gnu.org/licenses/.  *
47  *
48  *------------------------------------------------------------------------
49  ** See the file ChangeLog for a revision history.  */
50
51 /* [AS] Also useful here for debugging */
52 #ifdef WIN32
53 #include <windows.h>
54
55 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
56
57 #else
58
59 #define DoSleep( n ) if( (n) >= 0) sleep(n)
60
61 #endif
62
63 #include "config.h"
64
65 #include <assert.h>
66 #include <stdio.h>
67 #include <ctype.h>
68 #include <errno.h>
69 #include <sys/types.h>
70 #include <sys/stat.h>
71 #include <math.h>
72 #include <ctype.h>
73
74 #if STDC_HEADERS
75 # include <stdlib.h>
76 # include <string.h>
77 #else /* not STDC_HEADERS */
78 # if HAVE_STRING_H
79 #  include <string.h>
80 # else /* not HAVE_STRING_H */
81 #  include <strings.h>
82 # endif /* not HAVE_STRING_H */
83 #endif /* not STDC_HEADERS */
84
85 #if HAVE_SYS_FCNTL_H
86 # include <sys/fcntl.h>
87 #else /* not HAVE_SYS_FCNTL_H */
88 # if HAVE_FCNTL_H
89 #  include <fcntl.h>
90 # endif /* HAVE_FCNTL_H */
91 #endif /* not HAVE_SYS_FCNTL_H */
92
93 #if TIME_WITH_SYS_TIME
94 # include <sys/time.h>
95 # include <time.h>
96 #else
97 # if HAVE_SYS_TIME_H
98 #  include <sys/time.h>
99 # else
100 #  include <time.h>
101 # endif
102 #endif
103
104 #if defined(_amigados) && !defined(__GNUC__)
105 struct timezone {
106     int tz_minuteswest;
107     int tz_dsttime;
108 };
109 extern int gettimeofday(struct timeval *, struct timezone *);
110 #endif
111
112 #if HAVE_UNISTD_H
113 # include <unistd.h>
114 #endif
115
116 #include "common.h"
117 #include "frontend.h"
118 #include "backend.h"
119 #include "parser.h"
120 #include "moves.h"
121 #if ZIPPY
122 # include "zippy.h"
123 #endif
124 #include "backendz.h"
125 #include "gettext.h" 
126  
127 #ifdef ENABLE_NLS 
128 # define _(s) gettext (s) 
129 # define N_(s) gettext_noop (s) 
130 #else 
131 # define _(s) (s) 
132 # define N_(s) s 
133 #endif 
134
135
136 /* A point in time */
137 typedef struct {
138     long sec;  /* Assuming this is >= 32 bits */
139     int ms;    /* Assuming this is >= 16 bits */
140 } TimeMark;
141
142 int establish P((void));
143 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
144                          char *buf, int count, int error));
145 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
146                       char *buf, int count, int error));
147 void SendToICS P((char *s));
148 void SendToICSDelayed P((char *s, long msdelay));
149 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
150                       int toX, int toY));
151 void InitPosition P((int redraw));
152 void HandleMachineMove P((char *message, ChessProgramState *cps));
153 int AutoPlayOneMove P((void));
154 int LoadGameOneMove P((ChessMove readAhead));
155 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
156 int LoadPositionFromFile P((char *filename, int n, char *title));
157 int SavePositionToFile P((char *filename));
158 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
159                   Board board, char *castle, char *ep));
160 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
161 void ShowMove P((int fromX, int fromY, int toX, int toY));
162 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
163                    /*char*/int promoChar));
164 void BackwardInner P((int target));
165 void ForwardInner P((int target));
166 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
167 void EditPositionDone P((void));
168 void PrintOpponents P((FILE *fp));
169 void PrintPosition P((FILE *fp, int move));
170 void StartChessProgram P((ChessProgramState *cps));
171 void SendToProgram P((char *message, ChessProgramState *cps));
172 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
173 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
174                            char *buf, int count, int error));
175 void SendTimeControl P((ChessProgramState *cps,
176                         int mps, long tc, int inc, int sd, int st));
177 char *TimeControlTagValue P((void));
178 void Attention P((ChessProgramState *cps));
179 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
180 void ResurrectChessProgram P((void));
181 void DisplayComment P((int moveNumber, char *text));
182 void DisplayMove P((int moveNumber));
183 void DisplayAnalysis P((void));
184
185 void ParseGameHistory P((char *game));
186 void ParseBoard12 P((char *string));
187 void StartClocks P((void));
188 void SwitchClocks P((void));
189 void StopClocks P((void));
190 void ResetClocks P((void));
191 char *PGNDate P((void));
192 void SetGameInfo P((void));
193 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
194 int RegisterMove P((void));
195 void MakeRegisteredMove P((void));
196 void TruncateGame P((void));
197 int looking_at P((char *, int *, char *));
198 void CopyPlayerNameIntoFileName P((char **, char *));
199 char *SavePart P((char *));
200 int SaveGameOldStyle P((FILE *));
201 int SaveGamePGN P((FILE *));
202 void GetTimeMark P((TimeMark *));
203 long SubtractTimeMarks P((TimeMark *, TimeMark *));
204 int CheckFlags P((void));
205 long NextTickLength P((long));
206 void CheckTimeControl P((void));
207 void show_bytes P((FILE *, char *, int));
208 int string_to_rating P((char *str));
209 void ParseFeatures P((char* args, ChessProgramState *cps));
210 void InitBackEnd3 P((void));
211 void FeatureDone P((ChessProgramState* cps, int val));
212 void InitChessProgram P((ChessProgramState *cps, int setup));
213 void OutputKibitz(int window, char *text);
214 int PerpetualChase(int first, int last);
215 int EngineOutputIsUp();
216 void InitDrawingSizes(int x, int y);
217
218 #ifdef WIN32
219        extern void ConsoleCreate();
220 #endif
221
222 ChessProgramState *WhitePlayer();
223 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
224 int VerifyDisplayMode P(());
225
226 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
227 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
228 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
229 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
230 extern char installDir[MSG_SIZ];
231
232 extern int tinyLayout, smallLayout;
233 ChessProgramStats programStats;
234 static int exiting = 0; /* [HGM] moved to top */
235 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
236 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
237 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
238 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
239 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
240 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
241 int opponentKibitzes;
242
243 /* States for ics_getting_history */
244 #define H_FALSE 0
245 #define H_REQUESTED 1
246 #define H_GOT_REQ_HEADER 2
247 #define H_GOT_UNREQ_HEADER 3
248 #define H_GETTING_MOVES 4
249 #define H_GOT_UNWANTED_HEADER 5
250
251 /* whosays values for GameEnds */
252 #define GE_ICS 0
253 #define GE_ENGINE 1
254 #define GE_PLAYER 2
255 #define GE_FILE 3
256 #define GE_XBOARD 4
257 #define GE_ENGINE1 5
258 #define GE_ENGINE2 6
259
260 /* Maximum number of games in a cmail message */
261 #define CMAIL_MAX_GAMES 20
262
263 /* Different types of move when calling RegisterMove */
264 #define CMAIL_MOVE   0
265 #define CMAIL_RESIGN 1
266 #define CMAIL_DRAW   2
267 #define CMAIL_ACCEPT 3
268
269 /* Different types of result to remember for each game */
270 #define CMAIL_NOT_RESULT 0
271 #define CMAIL_OLD_RESULT 1
272 #define CMAIL_NEW_RESULT 2
273
274 /* Telnet protocol constants */
275 #define TN_WILL 0373
276 #define TN_WONT 0374
277 #define TN_DO   0375
278 #define TN_DONT 0376
279 #define TN_IAC  0377
280 #define TN_ECHO 0001
281 #define TN_SGA  0003
282 #define TN_PORT 23
283
284 /* [AS] */
285 static char * safeStrCpy( char * dst, const char * src, size_t count )
286 {
287     assert( dst != NULL );
288     assert( src != NULL );
289     assert( count > 0 );
290
291     strncpy( dst, src, count );
292     dst[ count-1 ] = '\0';
293     return dst;
294 }
295
296 #if 0
297 //[HGM] for future use? Conditioned out for now to suppress warning.
298 static char * safeStrCat( char * dst, const char * src, size_t count )
299 {
300     size_t  dst_len;
301
302     assert( dst != NULL );
303     assert( src != NULL );
304     assert( count > 0 );
305
306     dst_len = strlen(dst);
307
308     assert( count > dst_len ); /* Buffer size must be greater than current length */
309
310     safeStrCpy( dst + dst_len, src, count - dst_len );
311
312     return dst;
313 }
314 #endif
315
316 /* Some compiler can't cast u64 to double
317  * This function do the job for us:
318
319  * We use the highest bit for cast, this only
320  * works if the highest bit is not
321  * in use (This should not happen)
322  *
323  * We used this for all compiler
324  */
325 double
326 u64ToDouble(u64 value)
327 {
328   double r;
329   u64 tmp = value & u64Const(0x7fffffffffffffff);
330   r = (double)(s64)tmp;
331   if (value & u64Const(0x8000000000000000))
332        r +=  9.2233720368547758080e18; /* 2^63 */
333  return r;
334 }
335
336 /* Fake up flags for now, as we aren't keeping track of castling
337    availability yet. [HGM] Change of logic: the flag now only
338    indicates the type of castlings allowed by the rule of the game.
339    The actual rights themselves are maintained in the array
340    castlingRights, as part of the game history, and are not probed
341    by this function.
342  */
343 int
344 PosFlags(index)
345 {
346   int flags = F_ALL_CASTLE_OK;
347   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
348   switch (gameInfo.variant) {
349   case VariantSuicide:
350     flags &= ~F_ALL_CASTLE_OK;
351   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
352     flags |= F_IGNORE_CHECK;
353   case VariantLosers:
354     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
355     break;
356   case VariantAtomic:
357     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
358     break;
359   case VariantKriegspiel:
360     flags |= F_KRIEGSPIEL_CAPTURE;
361     break;
362   case VariantCapaRandom: 
363   case VariantFischeRandom:
364     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
365   case VariantNoCastle:
366   case VariantShatranj:
367   case VariantCourier:
368     flags &= ~F_ALL_CASTLE_OK;
369     break;
370   default:
371     break;
372   }
373   return flags;
374 }
375
376 FILE *gameFileFP, *debugFP;
377
378 /* 
379     [AS] Note: sometimes, the sscanf() function is used to parse the input
380     into a fixed-size buffer. Because of this, we must be prepared to
381     receive strings as long as the size of the input buffer, which is currently
382     set to 4K for Windows and 8K for the rest.
383     So, we must either allocate sufficiently large buffers here, or
384     reduce the size of the input buffer in the input reading part.
385 */
386
387 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
388 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
389 char thinkOutput1[MSG_SIZ*10];
390
391 ChessProgramState first, second;
392
393 /* premove variables */
394 int premoveToX = 0;
395 int premoveToY = 0;
396 int premoveFromX = 0;
397 int premoveFromY = 0;
398 int premovePromoChar = 0;
399 int gotPremove = 0;
400 Boolean alarmSounded;
401 /* end premove variables */
402
403 char *ics_prefix = "$";
404 int ics_type = ICS_GENERIC;
405
406 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
407 int pauseExamForwardMostMove = 0;
408 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
409 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
410 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
411 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
412 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
413 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
414 int whiteFlag = FALSE, blackFlag = FALSE;
415 int userOfferedDraw = FALSE;
416 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
417 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
418 int cmailMoveType[CMAIL_MAX_GAMES];
419 long ics_clock_paused = 0;
420 ProcRef icsPR = NoProc, cmailPR = NoProc;
421 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
422 GameMode gameMode = BeginningOfGame;
423 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
424 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
425 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
426 int hiddenThinkOutputState = 0; /* [AS] */
427 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
428 int adjudicateLossPlies = 6;
429 char white_holding[64], black_holding[64];
430 TimeMark lastNodeCountTime;
431 long lastNodeCount=0;
432 int have_sent_ICS_logon = 0;
433 int movesPerSession;
434 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
435 long timeControl_2; /* [AS] Allow separate time controls */
436 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
437 long timeRemaining[2][MAX_MOVES];
438 int matchGame = 0;
439 TimeMark programStartTime;
440 char ics_handle[MSG_SIZ];
441 int have_set_title = 0;
442
443 /* animateTraining preserves the state of appData.animate
444  * when Training mode is activated. This allows the
445  * response to be animated when appData.animate == TRUE and
446  * appData.animateDragging == TRUE.
447  */
448 Boolean animateTraining;
449
450 GameInfo gameInfo;
451
452 AppData appData;
453
454 Board boards[MAX_MOVES];
455 /* [HGM] Following 7 needed for accurate legality tests: */
456 char  epStatus[MAX_MOVES];
457 char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
458 char  castlingRank[BOARD_SIZE]; // and corresponding ranks
459 char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
460 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
461 int   initialRulePlies, FENrulePlies;
462 char  FENepStatus;
463 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
464 int loadFlag = 0; 
465 int shuffleOpenings;
466
467 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
468     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
469         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
470     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
471         BlackKing, BlackBishop, BlackKnight, BlackRook }
472 };
473
474 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
475     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
476         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
477     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
478         BlackKing, BlackKing, BlackKnight, BlackRook }
479 };
480
481 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
482     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
483         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
484     { BlackRook, BlackMan, BlackBishop, BlackQueen,
485         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
486 };
487
488 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
489     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
490         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
491     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
492         BlackKing, BlackBishop, BlackKnight, BlackRook }
493 };
494
495 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
496     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
497         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
498     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
499         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
500 };
501
502
503 #if (BOARD_SIZE>=10)
504 ChessSquare ShogiArray[2][BOARD_SIZE] = {
505     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
506         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
507     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
508         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
509 };
510
511 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
512     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
513         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
514     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
515         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
516 };
517
518 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
519     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
520         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
521     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
522         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
523 };
524
525 ChessSquare GreatArray[2][BOARD_SIZE] = {
526     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
527         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
528     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
529         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
530 };
531
532 ChessSquare JanusArray[2][BOARD_SIZE] = {
533     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
534         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
535     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
536         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
537 };
538
539 #ifdef GOTHIC
540 ChessSquare GothicArray[2][BOARD_SIZE] = {
541     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
542         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
544         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
545 };
546 #else // !GOTHIC
547 #define GothicArray CapablancaArray
548 #endif // !GOTHIC
549
550 #ifdef FALCON
551 ChessSquare FalconArray[2][BOARD_SIZE] = {
552     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
553         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
554     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
555         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
556 };
557 #else // !FALCON
558 #define FalconArray CapablancaArray
559 #endif // !FALCON
560
561 #else // !(BOARD_SIZE>=10)
562 #define XiangqiPosition FIDEArray
563 #define CapablancaArray FIDEArray
564 #define GothicArray FIDEArray
565 #define GreatArray FIDEArray
566 #endif // !(BOARD_SIZE>=10)
567
568 #if (BOARD_SIZE>=12)
569 ChessSquare CourierArray[2][BOARD_SIZE] = {
570     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
571         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
572     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
573         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
574 };
575 #else // !(BOARD_SIZE>=12)
576 #define CourierArray CapablancaArray
577 #endif // !(BOARD_SIZE>=12)
578
579
580 Board initialPosition;
581
582
583 /* Convert str to a rating. Checks for special cases of "----",
584
585    "++++", etc. Also strips ()'s */
586 int
587 string_to_rating(str)
588   char *str;
589 {
590   while(*str && !isdigit(*str)) ++str;
591   if (!*str)
592     return 0;   /* One of the special "no rating" cases */
593   else
594     return atoi(str);
595 }
596
597 void
598 ClearProgramStats()
599 {
600     /* Init programStats */
601     programStats.movelist[0] = 0;
602     programStats.depth = 0;
603     programStats.nr_moves = 0;
604     programStats.moves_left = 0;
605     programStats.nodes = 0;
606     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
607     programStats.score = 0;
608     programStats.got_only_move = 0;
609     programStats.got_fail = 0;
610     programStats.line_is_book = 0;
611 }
612
613 void
614 InitBackEnd1()
615 {
616     int matched, min, sec;
617
618     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
619
620     GetTimeMark(&programStartTime);
621     srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
622
623     ClearProgramStats();
624     programStats.ok_to_send = 1;
625     programStats.seen_stat = 0;
626
627     /*
628      * Initialize game list
629      */
630     ListNew(&gameList);
631
632
633     /*
634      * Internet chess server status
635      */
636     if (appData.icsActive) {
637         appData.matchMode = FALSE;
638         appData.matchGames = 0;
639 #if ZIPPY       
640         appData.noChessProgram = !appData.zippyPlay;
641 #else
642         appData.zippyPlay = FALSE;
643         appData.zippyTalk = FALSE;
644         appData.noChessProgram = TRUE;
645 #endif
646         if (*appData.icsHelper != NULLCHAR) {
647             appData.useTelnet = TRUE;
648             appData.telnetProgram = appData.icsHelper;
649         }
650     } else {
651         appData.zippyTalk = appData.zippyPlay = FALSE;
652     }
653
654     /* [AS] Initialize pv info list [HGM] and game state */
655     {
656         int i, j;
657
658         for( i=0; i<MAX_MOVES; i++ ) {
659             pvInfoList[i].depth = -1;
660             epStatus[i]=EP_NONE;
661             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
662         }
663     }
664
665     /*
666      * Parse timeControl resource
667      */
668     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
669                           appData.movesPerSession)) {
670         char buf[MSG_SIZ];
671         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
672         DisplayFatalError(buf, 0, 2);
673     }
674
675     /*
676      * Parse searchTime resource
677      */
678     if (*appData.searchTime != NULLCHAR) {
679         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
680         if (matched == 1) {
681             searchTime = min * 60;
682         } else if (matched == 2) {
683             searchTime = min * 60 + sec;
684         } else {
685             char buf[MSG_SIZ];
686             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
687             DisplayFatalError(buf, 0, 2);
688         }
689     }
690
691     /* [AS] Adjudication threshold */
692     adjudicateLossThreshold = appData.adjudicateLossThreshold;
693     
694     first.which = "first";
695     second.which = "second";
696     first.maybeThinking = second.maybeThinking = FALSE;
697     first.pr = second.pr = NoProc;
698     first.isr = second.isr = NULL;
699     first.sendTime = second.sendTime = 2;
700     first.sendDrawOffers = 1;
701     if (appData.firstPlaysBlack) {
702         first.twoMachinesColor = "black\n";
703         second.twoMachinesColor = "white\n";
704     } else {
705         first.twoMachinesColor = "white\n";
706         second.twoMachinesColor = "black\n";
707     }
708     first.program = appData.firstChessProgram;
709     second.program = appData.secondChessProgram;
710     first.host = appData.firstHost;
711     second.host = appData.secondHost;
712     first.dir = appData.firstDirectory;
713     second.dir = appData.secondDirectory;
714     first.other = &second;
715     second.other = &first;
716     first.initString = appData.initString;
717     second.initString = appData.secondInitString;
718     first.computerString = appData.firstComputerString;
719     second.computerString = appData.secondComputerString;
720     first.useSigint = second.useSigint = TRUE;
721     first.useSigterm = second.useSigterm = TRUE;
722     first.reuse = appData.reuseFirst;
723     second.reuse = appData.reuseSecond;
724     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
725     second.nps = appData.secondNPS;
726     first.useSetboard = second.useSetboard = FALSE;
727     first.useSAN = second.useSAN = FALSE;
728     first.usePing = second.usePing = FALSE;
729     first.lastPing = second.lastPing = 0;
730     first.lastPong = second.lastPong = 0;
731     first.usePlayother = second.usePlayother = FALSE;
732     first.useColors = second.useColors = TRUE;
733     first.useUsermove = second.useUsermove = FALSE;
734     first.sendICS = second.sendICS = FALSE;
735     first.sendName = second.sendName = appData.icsActive;
736     first.sdKludge = second.sdKludge = FALSE;
737     first.stKludge = second.stKludge = FALSE;
738     TidyProgramName(first.program, first.host, first.tidy);
739     TidyProgramName(second.program, second.host, second.tidy);
740     first.matchWins = second.matchWins = 0;
741     strcpy(first.variants, appData.variant);
742     strcpy(second.variants, appData.variant);
743     first.analysisSupport = second.analysisSupport = 2; /* detect */
744     first.analyzing = second.analyzing = FALSE;
745     first.initDone = second.initDone = FALSE;
746
747     /* New features added by Tord: */
748     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
749     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
750     /* End of new features added by Tord. */
751     first.fenOverride  = appData.fenOverride1;
752     second.fenOverride = appData.fenOverride2;
753
754     /* [HGM] time odds: set factor for each machine */
755     first.timeOdds  = appData.firstTimeOdds;
756     second.timeOdds = appData.secondTimeOdds;
757     { int norm = 1;
758         if(appData.timeOddsMode) {
759             norm = first.timeOdds;
760             if(norm > second.timeOdds) norm = second.timeOdds;
761         }
762         first.timeOdds /= norm;
763         second.timeOdds /= norm;
764     }
765
766     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
767     first.accumulateTC = appData.firstAccumulateTC;
768     second.accumulateTC = appData.secondAccumulateTC;
769     first.maxNrOfSessions = second.maxNrOfSessions = 1;
770
771     /* [HGM] debug */
772     first.debug = second.debug = FALSE;
773     first.supportsNPS = second.supportsNPS = UNKNOWN;
774
775     /* [HGM] options */
776     first.optionSettings  = appData.firstOptions;
777     second.optionSettings = appData.secondOptions;
778
779     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
780     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
781     first.isUCI = appData.firstIsUCI; /* [AS] */
782     second.isUCI = appData.secondIsUCI; /* [AS] */
783     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
784     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
785
786     if (appData.firstProtocolVersion > PROTOVER ||
787         appData.firstProtocolVersion < 1) {
788       char buf[MSG_SIZ];
789       sprintf(buf, _("protocol version %d not supported"),
790               appData.firstProtocolVersion);
791       DisplayFatalError(buf, 0, 2);
792     } else {
793       first.protocolVersion = appData.firstProtocolVersion;
794     }
795
796     if (appData.secondProtocolVersion > PROTOVER ||
797         appData.secondProtocolVersion < 1) {
798       char buf[MSG_SIZ];
799       sprintf(buf, _("protocol version %d not supported"),
800               appData.secondProtocolVersion);
801       DisplayFatalError(buf, 0, 2);
802     } else {
803       second.protocolVersion = appData.secondProtocolVersion;
804     }
805
806     if (appData.icsActive) {
807         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
808     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
809         appData.clockMode = FALSE;
810         first.sendTime = second.sendTime = 0;
811     }
812     
813 #if ZIPPY
814     /* Override some settings from environment variables, for backward
815        compatibility.  Unfortunately it's not feasible to have the env
816        vars just set defaults, at least in xboard.  Ugh.
817     */
818     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
819       ZippyInit();
820     }
821 #endif
822     
823     if (appData.noChessProgram) {
824         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
825         sprintf(programVersion, "%s", PACKAGE_STRING);
826     } else {
827 #if 0
828         char *p, *q;
829         q = first.program;
830         while (*q != ' ' && *q != NULLCHAR) q++;
831         p = q;
832         while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] backslash added */
833         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING + (q - p));
834         sprintf(programVersion, "%s + ", PACKAGE_STRING);
835         strncat(programVersion, p, q - p);
836 #else
837         /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
838         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
839         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
840 #endif
841     }
842
843     if (!appData.icsActive) {
844       char buf[MSG_SIZ];
845       /* Check for variants that are supported only in ICS mode,
846          or not at all.  Some that are accepted here nevertheless
847          have bugs; see comments below.
848       */
849       VariantClass variant = StringToVariant(appData.variant);
850       switch (variant) {
851       case VariantBughouse:     /* need four players and two boards */
852       case VariantKriegspiel:   /* need to hide pieces and move details */
853       /* case VariantFischeRandom: (Fabien: moved below) */
854         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
855         DisplayFatalError(buf, 0, 2);
856         return;
857
858       case VariantUnknown:
859       case VariantLoadable:
860       case Variant29:
861       case Variant30:
862       case Variant31:
863       case Variant32:
864       case Variant33:
865       case Variant34:
866       case Variant35:
867       case Variant36:
868       default:
869         sprintf(buf, _("Unknown variant name %s"), appData.variant);
870         DisplayFatalError(buf, 0, 2);
871         return;
872
873       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
874       case VariantFairy:      /* [HGM] TestLegality definitely off! */
875       case VariantGothic:     /* [HGM] should work */
876       case VariantCapablanca: /* [HGM] should work */
877       case VariantCourier:    /* [HGM] initial forced moves not implemented */
878       case VariantShogi:      /* [HGM] drops not tested for legality */
879       case VariantKnightmate: /* [HGM] should work */
880       case VariantCylinder:   /* [HGM] untested */
881       case VariantFalcon:     /* [HGM] untested */
882       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
883                                  offboard interposition not understood */
884       case VariantNormal:     /* definitely works! */
885       case VariantWildCastle: /* pieces not automatically shuffled */
886       case VariantNoCastle:   /* pieces not automatically shuffled */
887       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
888       case VariantLosers:     /* should work except for win condition,
889                                  and doesn't know captures are mandatory */
890       case VariantSuicide:    /* should work except for win condition,
891                                  and doesn't know captures are mandatory */
892       case VariantGiveaway:   /* should work except for win condition,
893                                  and doesn't know captures are mandatory */
894       case VariantTwoKings:   /* should work */
895       case VariantAtomic:     /* should work except for win condition */
896       case Variant3Check:     /* should work except for win condition */
897       case VariantShatranj:   /* should work except for all win conditions */
898       case VariantBerolina:   /* might work if TestLegality is off */
899       case VariantCapaRandom: /* should work */
900       case VariantJanus:      /* should work */
901       case VariantSuper:      /* experimental */
902       case VariantGreat:      /* experimental, requires legality testing to be off */
903         break;
904       }
905     }
906
907     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
908     InitEngineUCI( installDir, &second );
909 }
910
911 int NextIntegerFromString( char ** str, long * value )
912 {
913     int result = -1;
914     char * s = *str;
915
916     while( *s == ' ' || *s == '\t' ) {
917         s++;
918     }
919
920     *value = 0;
921
922     if( *s >= '0' && *s <= '9' ) {
923         while( *s >= '0' && *s <= '9' ) {
924             *value = *value * 10 + (*s - '0');
925             s++;
926         }
927
928         result = 0;
929     }
930
931     *str = s;
932
933     return result;
934 }
935
936 int NextTimeControlFromString( char ** str, long * value )
937 {
938     long temp;
939     int result = NextIntegerFromString( str, &temp );
940
941     if( result == 0 ) {
942         *value = temp * 60; /* Minutes */
943         if( **str == ':' ) {
944             (*str)++;
945             result = NextIntegerFromString( str, &temp );
946             *value += temp; /* Seconds */
947         }
948     }
949
950     return result;
951 }
952
953 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
954 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
955     int result = -1; long temp, temp2;
956
957     if(**str != '+') return -1; // old params remain in force!
958     (*str)++;
959     if( NextTimeControlFromString( str, &temp ) ) return -1;
960
961     if(**str != '/') {
962         /* time only: incremental or sudden-death time control */
963         if(**str == '+') { /* increment follows; read it */
964             (*str)++;
965             if(result = NextIntegerFromString( str, &temp2)) return -1;
966             *inc = temp2 * 1000;
967         } else *inc = 0;
968         *moves = 0; *tc = temp * 1000; 
969         return 0;
970     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
971
972     (*str)++; /* classical time control */
973     result = NextTimeControlFromString( str, &temp2);
974     if(result == 0) {
975         *moves = temp/60;
976         *tc    = temp2 * 1000;
977         *inc   = 0;
978     }
979     return result;
980 }
981
982 int GetTimeQuota(int movenr)
983 {   /* [HGM] get time to add from the multi-session time-control string */
984     int moves=1; /* kludge to force reading of first session */
985     long time, increment;
986     char *s = fullTimeControlString;
987
988     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
989     do {
990         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
991         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
992         if(movenr == -1) return time;    /* last move before new session     */
993         if(!moves) return increment;     /* current session is incremental   */
994         if(movenr >= 0) movenr -= moves; /* we already finished this session */
995     } while(movenr >= -1);               /* try again for next session       */
996
997     return 0; // no new time quota on this move
998 }
999
1000 int
1001 ParseTimeControl(tc, ti, mps)
1002      char *tc;
1003      int ti;
1004      int mps;
1005 {
1006 #if 0
1007     int matched, min, sec;
1008
1009     matched = sscanf(tc, "%d:%d", &min, &sec);
1010     if (matched == 1) {
1011         timeControl = min * 60 * 1000;
1012     } else if (matched == 2) {
1013         timeControl = (min * 60 + sec) * 1000;
1014     } else {
1015         return FALSE;
1016     }
1017 #else
1018     long tc1;
1019     long tc2;
1020     char buf[MSG_SIZ];
1021
1022     if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1023     if(ti > 0) {
1024         if(mps)
1025              sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1026         else sprintf(buf, "+%s+%d", tc, ti);
1027     } else {
1028         if(mps)
1029              sprintf(buf, "+%d/%s", mps, tc);
1030         else sprintf(buf, "+%s", tc);
1031     }
1032     fullTimeControlString = StrSave(buf);
1033
1034     if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1035         return FALSE;
1036     }
1037
1038     if( *tc == '/' ) {
1039         /* Parse second time control */
1040         tc++;
1041
1042         if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1043             return FALSE;
1044         }
1045
1046         if( tc2 == 0 ) {
1047             return FALSE;
1048         }
1049
1050         timeControl_2 = tc2 * 1000;
1051     }
1052     else {
1053         timeControl_2 = 0;
1054     }
1055
1056     if( tc1 == 0 ) {
1057         return FALSE;
1058     }
1059
1060     timeControl = tc1 * 1000;
1061 #endif
1062
1063     if (ti >= 0) {
1064         timeIncrement = ti * 1000;  /* convert to ms */
1065         movesPerSession = 0;
1066     } else {
1067         timeIncrement = 0;
1068         movesPerSession = mps;
1069     }
1070     return TRUE;
1071 }
1072
1073 void
1074 InitBackEnd2()
1075 {
1076     if (appData.debugMode) {
1077         fprintf(debugFP, "%s\n", programVersion);
1078     }
1079
1080     if (appData.matchGames > 0) {
1081         appData.matchMode = TRUE;
1082     } else if (appData.matchMode) {
1083         appData.matchGames = 1;
1084     }
1085     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1086         appData.matchGames = appData.sameColorGames;
1087     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1088         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1089         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1090     }
1091     Reset(TRUE, FALSE);
1092     if (appData.noChessProgram || first.protocolVersion == 1) {
1093       InitBackEnd3();
1094     } else {
1095       /* kludge: allow timeout for initial "feature" commands */
1096       FreezeUI();
1097       DisplayMessage("", _("Starting chess program"));
1098       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1099     }
1100 }
1101
1102 void
1103 InitBackEnd3 P((void))
1104 {
1105     GameMode initialMode;
1106     char buf[MSG_SIZ];
1107     int err;
1108
1109     InitChessProgram(&first, startedFromSetupPosition);
1110
1111
1112     if (appData.icsActive) {
1113 #ifdef WIN32
1114         /* [DM] Make a console window if needed [HGM] merged ifs */
1115         ConsoleCreate(); 
1116 #endif
1117         err = establish();
1118         if (err != 0) {
1119             if (*appData.icsCommPort != NULLCHAR) {
1120                 sprintf(buf, _("Could not open comm port %s"),  
1121                         appData.icsCommPort);
1122             } else {
1123                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1124                         appData.icsHost, appData.icsPort);
1125             }
1126             DisplayFatalError(buf, err, 1);
1127             return;
1128         }
1129         SetICSMode();
1130         telnetISR =
1131           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1132         fromUserISR =
1133           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1134     } else if (appData.noChessProgram) {
1135         SetNCPMode();
1136     } else {
1137         SetGNUMode();
1138     }
1139
1140     if (*appData.cmailGameName != NULLCHAR) {
1141         SetCmailMode();
1142         OpenLoopback(&cmailPR);
1143         cmailISR =
1144           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1145     }
1146     
1147     ThawUI();
1148     DisplayMessage("", "");
1149     if (StrCaseCmp(appData.initialMode, "") == 0) {
1150       initialMode = BeginningOfGame;
1151     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1152       initialMode = TwoMachinesPlay;
1153     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1154       initialMode = AnalyzeFile; 
1155     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1156       initialMode = AnalyzeMode;
1157     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1158       initialMode = MachinePlaysWhite;
1159     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1160       initialMode = MachinePlaysBlack;
1161     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1162       initialMode = EditGame;
1163     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1164       initialMode = EditPosition;
1165     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1166       initialMode = Training;
1167     } else {
1168       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1169       DisplayFatalError(buf, 0, 2);
1170       return;
1171     }
1172
1173     if (appData.matchMode) {
1174         /* Set up machine vs. machine match */
1175         if (appData.noChessProgram) {
1176             DisplayFatalError(_("Can't have a match with no chess programs"),
1177                               0, 2);
1178             return;
1179         }
1180         matchMode = TRUE;
1181         matchGame = 1;
1182         if (*appData.loadGameFile != NULLCHAR) {
1183             int index = appData.loadGameIndex; // [HGM] autoinc
1184             if(index<0) lastIndex = index = 1;
1185             if (!LoadGameFromFile(appData.loadGameFile,
1186                                   index,
1187                                   appData.loadGameFile, FALSE)) {
1188                 DisplayFatalError(_("Bad game file"), 0, 1);
1189                 return;
1190             }
1191         } else if (*appData.loadPositionFile != NULLCHAR) {
1192             int index = appData.loadPositionIndex; // [HGM] autoinc
1193             if(index<0) lastIndex = index = 1;
1194             if (!LoadPositionFromFile(appData.loadPositionFile,
1195                                       index,
1196                                       appData.loadPositionFile)) {
1197                 DisplayFatalError(_("Bad position file"), 0, 1);
1198                 return;
1199             }
1200         }
1201         TwoMachinesEvent();
1202     } else if (*appData.cmailGameName != NULLCHAR) {
1203         /* Set up cmail mode */
1204         ReloadCmailMsgEvent(TRUE);
1205     } else {
1206         /* Set up other modes */
1207         if (initialMode == AnalyzeFile) {
1208           if (*appData.loadGameFile == NULLCHAR) {
1209             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1210             return;
1211           }
1212         }
1213         if (*appData.loadGameFile != NULLCHAR) {
1214             (void) LoadGameFromFile(appData.loadGameFile,
1215                                     appData.loadGameIndex,
1216                                     appData.loadGameFile, TRUE);
1217         } else if (*appData.loadPositionFile != NULLCHAR) {
1218             (void) LoadPositionFromFile(appData.loadPositionFile,
1219                                         appData.loadPositionIndex,
1220                                         appData.loadPositionFile);
1221             /* [HGM] try to make self-starting even after FEN load */
1222             /* to allow automatic setup of fairy variants with wtm */
1223             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1224                 gameMode = BeginningOfGame;
1225                 setboardSpoiledMachineBlack = 1;
1226             }
1227             /* [HGM] loadPos: make that every new game uses the setup */
1228             /* from file as long as we do not switch variant          */
1229             if(!blackPlaysFirst) { int i;
1230                 startedFromPositionFile = TRUE;
1231                 CopyBoard(filePosition, boards[0]);
1232                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1233             }
1234         }
1235         if (initialMode == AnalyzeMode) {
1236           if (appData.noChessProgram) {
1237             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1238             return;
1239           }
1240           if (appData.icsActive) {
1241             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1242             return;
1243           }
1244           AnalyzeModeEvent();
1245         } else if (initialMode == AnalyzeFile) {
1246           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1247           ShowThinkingEvent();
1248           AnalyzeFileEvent();
1249           AnalysisPeriodicEvent(1);
1250         } else if (initialMode == MachinePlaysWhite) {
1251           if (appData.noChessProgram) {
1252             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1253                               0, 2);
1254             return;
1255           }
1256           if (appData.icsActive) {
1257             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1258                               0, 2);
1259             return;
1260           }
1261           MachineWhiteEvent();
1262         } else if (initialMode == MachinePlaysBlack) {
1263           if (appData.noChessProgram) {
1264             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1265                               0, 2);
1266             return;
1267           }
1268           if (appData.icsActive) {
1269             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1270                               0, 2);
1271             return;
1272           }
1273           MachineBlackEvent();
1274         } else if (initialMode == TwoMachinesPlay) {
1275           if (appData.noChessProgram) {
1276             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1277                               0, 2);
1278             return;
1279           }
1280           if (appData.icsActive) {
1281             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1282                               0, 2);
1283             return;
1284           }
1285           TwoMachinesEvent();
1286         } else if (initialMode == EditGame) {
1287           EditGameEvent();
1288         } else if (initialMode == EditPosition) {
1289           EditPositionEvent();
1290         } else if (initialMode == Training) {
1291           if (*appData.loadGameFile == NULLCHAR) {
1292             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1293             return;
1294           }
1295           TrainingEvent();
1296         }
1297     }
1298 }
1299
1300 /*
1301  * Establish will establish a contact to a remote host.port.
1302  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1303  *  used to talk to the host.
1304  * Returns 0 if okay, error code if not.
1305  */
1306 int
1307 establish()
1308 {
1309     char buf[MSG_SIZ];
1310
1311     if (*appData.icsCommPort != NULLCHAR) {
1312         /* Talk to the host through a serial comm port */
1313         return OpenCommPort(appData.icsCommPort, &icsPR);
1314
1315     } else if (*appData.gateway != NULLCHAR) {
1316         if (*appData.remoteShell == NULLCHAR) {
1317             /* Use the rcmd protocol to run telnet program on a gateway host */
1318             snprintf(buf, sizeof(buf), "%s %s %s",
1319                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1320             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1321
1322         } else {
1323             /* Use the rsh program to run telnet program on a gateway host */
1324             if (*appData.remoteUser == NULLCHAR) {
1325                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1326                         appData.gateway, appData.telnetProgram,
1327                         appData.icsHost, appData.icsPort);
1328             } else {
1329                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1330                         appData.remoteShell, appData.gateway, 
1331                         appData.remoteUser, appData.telnetProgram,
1332                         appData.icsHost, appData.icsPort);
1333             }
1334             return StartChildProcess(buf, "", &icsPR);
1335
1336         }
1337     } else if (appData.useTelnet) {
1338         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1339
1340     } else {
1341         /* TCP socket interface differs somewhat between
1342            Unix and NT; handle details in the front end.
1343            */
1344         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1345     }
1346 }
1347
1348 void
1349 show_bytes(fp, buf, count)
1350      FILE *fp;
1351      char *buf;
1352      int count;
1353 {
1354     while (count--) {
1355         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1356             fprintf(fp, "\\%03o", *buf & 0xff);
1357         } else {
1358             putc(*buf, fp);
1359         }
1360         buf++;
1361     }
1362     fflush(fp);
1363 }
1364
1365 /* Returns an errno value */
1366 int
1367 OutputMaybeTelnet(pr, message, count, outError)
1368      ProcRef pr;
1369      char *message;
1370      int count;
1371      int *outError;
1372 {
1373     char buf[8192], *p, *q, *buflim;
1374     int left, newcount, outcount;
1375
1376     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1377         *appData.gateway != NULLCHAR) {
1378         if (appData.debugMode) {
1379             fprintf(debugFP, ">ICS: ");
1380             show_bytes(debugFP, message, count);
1381             fprintf(debugFP, "\n");
1382         }
1383         return OutputToProcess(pr, message, count, outError);
1384     }
1385
1386     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1387     p = message;
1388     q = buf;
1389     left = count;
1390     newcount = 0;
1391     while (left) {
1392         if (q >= buflim) {
1393             if (appData.debugMode) {
1394                 fprintf(debugFP, ">ICS: ");
1395                 show_bytes(debugFP, buf, newcount);
1396                 fprintf(debugFP, "\n");
1397             }
1398             outcount = OutputToProcess(pr, buf, newcount, outError);
1399             if (outcount < newcount) return -1; /* to be sure */
1400             q = buf;
1401             newcount = 0;
1402         }
1403         if (*p == '\n') {
1404             *q++ = '\r';
1405             newcount++;
1406         } else if (((unsigned char) *p) == TN_IAC) {
1407             *q++ = (char) TN_IAC;
1408             newcount ++;
1409         }
1410         *q++ = *p++;
1411         newcount++;
1412         left--;
1413     }
1414     if (appData.debugMode) {
1415         fprintf(debugFP, ">ICS: ");
1416         show_bytes(debugFP, buf, newcount);
1417         fprintf(debugFP, "\n");
1418     }
1419     outcount = OutputToProcess(pr, buf, newcount, outError);
1420     if (outcount < newcount) return -1; /* to be sure */
1421     return count;
1422 }
1423
1424 void
1425 read_from_player(isr, closure, message, count, error)
1426      InputSourceRef isr;
1427      VOIDSTAR closure;
1428      char *message;
1429      int count;
1430      int error;
1431 {
1432     int outError, outCount;
1433     static int gotEof = 0;
1434
1435     /* Pass data read from player on to ICS */
1436     if (count > 0) {
1437         gotEof = 0;
1438         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1439         if (outCount < count) {
1440             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1441         }
1442     } else if (count < 0) {
1443         RemoveInputSource(isr);
1444         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1445     } else if (gotEof++ > 0) {
1446         RemoveInputSource(isr);
1447         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1448     }
1449 }
1450
1451 void
1452 SendToICS(s)
1453      char *s;
1454 {
1455     int count, outCount, outError;
1456
1457     if (icsPR == NULL) return;
1458
1459     count = strlen(s);
1460     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1461     if (outCount < count) {
1462         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1463     }
1464 }
1465
1466 /* This is used for sending logon scripts to the ICS. Sending
1467    without a delay causes problems when using timestamp on ICC
1468    (at least on my machine). */
1469 void
1470 SendToICSDelayed(s,msdelay)
1471      char *s;
1472      long msdelay;
1473 {
1474     int count, outCount, outError;
1475
1476     if (icsPR == NULL) return;
1477
1478     count = strlen(s);
1479     if (appData.debugMode) {
1480         fprintf(debugFP, ">ICS: ");
1481         show_bytes(debugFP, s, count);
1482         fprintf(debugFP, "\n");
1483     }
1484     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1485                                       msdelay);
1486     if (outCount < count) {
1487         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1488     }
1489 }
1490
1491
1492 /* Remove all highlighting escape sequences in s
1493    Also deletes any suffix starting with '(' 
1494    */
1495 char *
1496 StripHighlightAndTitle(s)
1497      char *s;
1498 {
1499     static char retbuf[MSG_SIZ];
1500     char *p = retbuf;
1501
1502     while (*s != NULLCHAR) {
1503         while (*s == '\033') {
1504             while (*s != NULLCHAR && !isalpha(*s)) s++;
1505             if (*s != NULLCHAR) s++;
1506         }
1507         while (*s != NULLCHAR && *s != '\033') {
1508             if (*s == '(' || *s == '[') {
1509                 *p = NULLCHAR;
1510                 return retbuf;
1511             }
1512             *p++ = *s++;
1513         }
1514     }
1515     *p = NULLCHAR;
1516     return retbuf;
1517 }
1518
1519 /* Remove all highlighting escape sequences in s */
1520 char *
1521 StripHighlight(s)
1522      char *s;
1523 {
1524     static char retbuf[MSG_SIZ];
1525     char *p = retbuf;
1526
1527     while (*s != NULLCHAR) {
1528         while (*s == '\033') {
1529             while (*s != NULLCHAR && !isalpha(*s)) s++;
1530             if (*s != NULLCHAR) s++;
1531         }
1532         while (*s != NULLCHAR && *s != '\033') {
1533             *p++ = *s++;
1534         }
1535     }
1536     *p = NULLCHAR;
1537     return retbuf;
1538 }
1539
1540 char *variantNames[] = VARIANT_NAMES;
1541 char *
1542 VariantName(v)
1543      VariantClass v;
1544 {
1545     return variantNames[v];
1546 }
1547
1548
1549 /* Identify a variant from the strings the chess servers use or the
1550    PGN Variant tag names we use. */
1551 VariantClass
1552 StringToVariant(e)
1553      char *e;
1554 {
1555     char *p;
1556     int wnum = -1;
1557     VariantClass v = VariantNormal;
1558     int i, found = FALSE;
1559     char buf[MSG_SIZ];
1560
1561     if (!e) return v;
1562
1563     /* [HGM] skip over optional board-size prefixes */
1564     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1565         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1566         while( *e++ != '_');
1567     }
1568
1569     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1570       if (StrCaseStr(e, variantNames[i])) {
1571         v = (VariantClass) i;
1572         found = TRUE;
1573         break;
1574       }
1575     }
1576
1577     if (!found) {
1578       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1579           || StrCaseStr(e, "wild/fr") 
1580           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1581         v = VariantFischeRandom;
1582       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1583                  (i = 1, p = StrCaseStr(e, "w"))) {
1584         p += i;
1585         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1586         if (isdigit(*p)) {
1587           wnum = atoi(p);
1588         } else {
1589           wnum = -1;
1590         }
1591         switch (wnum) {
1592         case 0: /* FICS only, actually */
1593         case 1:
1594           /* Castling legal even if K starts on d-file */
1595           v = VariantWildCastle;
1596           break;
1597         case 2:
1598         case 3:
1599         case 4:
1600           /* Castling illegal even if K & R happen to start in
1601              normal positions. */
1602           v = VariantNoCastle;
1603           break;
1604         case 5:
1605         case 7:
1606         case 8:
1607         case 10:
1608         case 11:
1609         case 12:
1610         case 13:
1611         case 14:
1612         case 15:
1613         case 18:
1614         case 19:
1615           /* Castling legal iff K & R start in normal positions */
1616           v = VariantNormal;
1617           break;
1618         case 6:
1619         case 20:
1620         case 21:
1621           /* Special wilds for position setup; unclear what to do here */
1622           v = VariantLoadable;
1623           break;
1624         case 9:
1625           /* Bizarre ICC game */
1626           v = VariantTwoKings;
1627           break;
1628         case 16:
1629           v = VariantKriegspiel;
1630           break;
1631         case 17:
1632           v = VariantLosers;
1633           break;
1634         case 22:
1635           v = VariantFischeRandom;
1636           break;
1637         case 23:
1638           v = VariantCrazyhouse;
1639           break;
1640         case 24:
1641           v = VariantBughouse;
1642           break;
1643         case 25:
1644           v = Variant3Check;
1645           break;
1646         case 26:
1647           /* Not quite the same as FICS suicide! */
1648           v = VariantGiveaway;
1649           break;
1650         case 27:
1651           v = VariantAtomic;
1652           break;
1653         case 28:
1654           v = VariantShatranj;
1655           break;
1656
1657         /* Temporary names for future ICC types.  The name *will* change in 
1658            the next xboard/WinBoard release after ICC defines it. */
1659         case 29:
1660           v = Variant29;
1661           break;
1662         case 30:
1663           v = Variant30;
1664           break;
1665         case 31:
1666           v = Variant31;
1667           break;
1668         case 32:
1669           v = Variant32;
1670           break;
1671         case 33:
1672           v = Variant33;
1673           break;
1674         case 34:
1675           v = Variant34;
1676           break;
1677         case 35:
1678           v = Variant35;
1679           break;
1680         case 36:
1681           v = Variant36;
1682           break;
1683         case 37:
1684           v = VariantShogi;
1685           break;
1686         case 38:
1687           v = VariantXiangqi;
1688           break;
1689         case 39:
1690           v = VariantCourier;
1691           break;
1692         case 40:
1693           v = VariantGothic;
1694           break;
1695         case 41:
1696           v = VariantCapablanca;
1697           break;
1698         case 42:
1699           v = VariantKnightmate;
1700           break;
1701         case 43:
1702           v = VariantFairy;
1703           break;
1704         case 44:
1705           v = VariantCylinder;
1706           break;
1707         case 45:
1708           v = VariantFalcon;
1709           break;
1710         case 46:
1711           v = VariantCapaRandom;
1712           break;
1713         case 47:
1714           v = VariantBerolina;
1715           break;
1716         case 48:
1717           v = VariantJanus;
1718           break;
1719         case 49:
1720           v = VariantSuper;
1721           break;
1722         case 50:
1723           v = VariantGreat;
1724           break;
1725         case -1:
1726           /* Found "wild" or "w" in the string but no number;
1727              must assume it's normal chess. */
1728           v = VariantNormal;
1729           break;
1730         default:
1731           sprintf(buf, _("Unknown wild type %d"), wnum);
1732           DisplayError(buf, 0);
1733           v = VariantUnknown;
1734           break;
1735         }
1736       }
1737     }
1738     if (appData.debugMode) {
1739       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1740               e, wnum, VariantName(v));
1741     }
1742     return v;
1743 }
1744
1745 static int leftover_start = 0, leftover_len = 0;
1746 char star_match[STAR_MATCH_N][MSG_SIZ];
1747
1748 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1749    advance *index beyond it, and set leftover_start to the new value of
1750    *index; else return FALSE.  If pattern contains the character '*', it
1751    matches any sequence of characters not containing '\r', '\n', or the
1752    character following the '*' (if any), and the matched sequence(s) are
1753    copied into star_match.
1754    */
1755 int
1756 looking_at(buf, index, pattern)
1757      char *buf;
1758      int *index;
1759      char *pattern;
1760 {
1761     char *bufp = &buf[*index], *patternp = pattern;
1762     int star_count = 0;
1763     char *matchp = star_match[0];
1764     
1765     for (;;) {
1766         if (*patternp == NULLCHAR) {
1767             *index = leftover_start = bufp - buf;
1768             *matchp = NULLCHAR;
1769             return TRUE;
1770         }
1771         if (*bufp == NULLCHAR) return FALSE;
1772         if (*patternp == '*') {
1773             if (*bufp == *(patternp + 1)) {
1774                 *matchp = NULLCHAR;
1775                 matchp = star_match[++star_count];
1776                 patternp += 2;
1777                 bufp++;
1778                 continue;
1779             } else if (*bufp == '\n' || *bufp == '\r') {
1780                 patternp++;
1781                 if (*patternp == NULLCHAR)
1782                   continue;
1783                 else
1784                   return FALSE;
1785             } else {
1786                 *matchp++ = *bufp++;
1787                 continue;
1788             }
1789         }
1790         if (*patternp != *bufp) return FALSE;
1791         patternp++;
1792         bufp++;
1793     }
1794 }
1795
1796 void
1797 SendToPlayer(data, length)
1798      char *data;
1799      int length;
1800 {
1801     int error, outCount;
1802     outCount = OutputToProcess(NoProc, data, length, &error);
1803     if (outCount < length) {
1804         DisplayFatalError(_("Error writing to display"), error, 1);
1805     }
1806 }
1807
1808 void
1809 PackHolding(packed, holding)
1810      char packed[];
1811      char *holding;
1812 {
1813     char *p = holding;
1814     char *q = packed;
1815     int runlength = 0;
1816     int curr = 9999;
1817     do {
1818         if (*p == curr) {
1819             runlength++;
1820         } else {
1821             switch (runlength) {
1822               case 0:
1823                 break;
1824               case 1:
1825                 *q++ = curr;
1826                 break;
1827               case 2:
1828                 *q++ = curr;
1829                 *q++ = curr;
1830                 break;
1831               default:
1832                 sprintf(q, "%d", runlength);
1833                 while (*q) q++;
1834                 *q++ = curr;
1835                 break;
1836             }
1837             runlength = 1;
1838             curr = *p;
1839         }
1840     } while (*p++);
1841     *q = NULLCHAR;
1842 }
1843
1844 /* Telnet protocol requests from the front end */
1845 void
1846 TelnetRequest(ddww, option)
1847      unsigned char ddww, option;
1848 {
1849     unsigned char msg[3];
1850     int outCount, outError;
1851
1852     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1853
1854     if (appData.debugMode) {
1855         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1856         switch (ddww) {
1857           case TN_DO:
1858             ddwwStr = "DO";
1859             break;
1860           case TN_DONT:
1861             ddwwStr = "DONT";
1862             break;
1863           case TN_WILL:
1864             ddwwStr = "WILL";
1865             break;
1866           case TN_WONT:
1867             ddwwStr = "WONT";
1868             break;
1869           default:
1870             ddwwStr = buf1;
1871             sprintf(buf1, "%d", ddww);
1872             break;
1873         }
1874         switch (option) {
1875           case TN_ECHO:
1876             optionStr = "ECHO";
1877             break;
1878           default:
1879             optionStr = buf2;
1880             sprintf(buf2, "%d", option);
1881             break;
1882         }
1883         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1884     }
1885     msg[0] = TN_IAC;
1886     msg[1] = ddww;
1887     msg[2] = option;
1888     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1889     if (outCount < 3) {
1890         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1891     }
1892 }
1893
1894 void
1895 DoEcho()
1896 {
1897     if (!appData.icsActive) return;
1898     TelnetRequest(TN_DO, TN_ECHO);
1899 }
1900
1901 void
1902 DontEcho()
1903 {
1904     if (!appData.icsActive) return;
1905     TelnetRequest(TN_DONT, TN_ECHO);
1906 }
1907
1908 void
1909 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1910 {
1911     /* put the holdings sent to us by the server on the board holdings area */
1912     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1913     char p;
1914     ChessSquare piece;
1915
1916     if(gameInfo.holdingsWidth < 2)  return;
1917
1918     if( (int)lowestPiece >= BlackPawn ) {
1919         holdingsColumn = 0;
1920         countsColumn = 1;
1921         holdingsStartRow = BOARD_HEIGHT-1;
1922         direction = -1;
1923     } else {
1924         holdingsColumn = BOARD_WIDTH-1;
1925         countsColumn = BOARD_WIDTH-2;
1926         holdingsStartRow = 0;
1927         direction = 1;
1928     }
1929
1930     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1931         board[i][holdingsColumn] = EmptySquare;
1932         board[i][countsColumn]   = (ChessSquare) 0;
1933     }
1934     while( (p=*holdings++) != NULLCHAR ) {
1935         piece = CharToPiece( ToUpper(p) );
1936         if(piece == EmptySquare) continue;
1937         /*j = (int) piece - (int) WhitePawn;*/
1938         j = PieceToNumber(piece);
1939         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1940         if(j < 0) continue;               /* should not happen */
1941         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1942         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1943         board[holdingsStartRow+j*direction][countsColumn]++;
1944     }
1945
1946 }
1947
1948
1949 void
1950 VariantSwitch(Board board, VariantClass newVariant)
1951 {
1952    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1953    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1954 //   Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1955
1956    startedFromPositionFile = FALSE;
1957    if(gameInfo.variant == newVariant) return;
1958
1959    /* [HGM] This routine is called each time an assignment is made to
1960     * gameInfo.variant during a game, to make sure the board sizes
1961     * are set to match the new variant. If that means adding or deleting
1962     * holdings, we shift the playing board accordingly
1963     * This kludge is needed because in ICS observe mode, we get boards
1964     * of an ongoing game without knowing the variant, and learn about the
1965     * latter only later. This can be because of the move list we requested,
1966     * in which case the game history is refilled from the beginning anyway,
1967     * but also when receiving holdings of a crazyhouse game. In the latter
1968     * case we want to add those holdings to the already received position.
1969     */
1970
1971
1972   if (appData.debugMode) {
1973     fprintf(debugFP, "Switch board from %s to %s\n",
1974                VariantName(gameInfo.variant), VariantName(newVariant));
1975     setbuf(debugFP, NULL);
1976   }
1977     shuffleOpenings = 0;       /* [HGM] shuffle */
1978     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1979     switch(newVariant) {
1980             case VariantShogi:
1981               newWidth = 9;  newHeight = 9;
1982               gameInfo.holdingsSize = 7;
1983             case VariantBughouse:
1984             case VariantCrazyhouse:
1985               newHoldingsWidth = 2; break;
1986             default:
1987               newHoldingsWidth = gameInfo.holdingsSize = 0;
1988     }
1989
1990     if(newWidth  != gameInfo.boardWidth  ||
1991        newHeight != gameInfo.boardHeight ||
1992        newHoldingsWidth != gameInfo.holdingsWidth ) {
1993
1994         /* shift position to new playing area, if needed */
1995         if(newHoldingsWidth > gameInfo.holdingsWidth) {
1996            for(i=0; i<BOARD_HEIGHT; i++) 
1997                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1998                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1999                                                      board[i][j];
2000            for(i=0; i<newHeight; i++) {
2001                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2002                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2003            }
2004         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2005            for(i=0; i<BOARD_HEIGHT; i++)
2006                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2007                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2008                                                  board[i][j];
2009         }
2010
2011         gameInfo.boardWidth  = newWidth;
2012         gameInfo.boardHeight = newHeight;
2013         gameInfo.holdingsWidth = newHoldingsWidth;
2014         gameInfo.variant = newVariant;
2015         InitDrawingSizes(-2, 0);
2016
2017         /* [HGM] The following should definitely be solved in a better way */
2018 #if 0
2019         CopyBoard(board, tempBoard); /* save position in case it is board[0] */
2020         for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
2021         saveEP = epStatus[0];
2022 #endif
2023         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2024 #if 0
2025         epStatus[0] = saveEP;
2026         for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
2027         CopyBoard(tempBoard, board); /* restore position received from ICS   */
2028 #endif
2029     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2030
2031     forwardMostMove = oldForwardMostMove;
2032     backwardMostMove = oldBackwardMostMove;
2033     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2034 }
2035
2036 static int loggedOn = FALSE;
2037
2038 /*-- Game start info cache: --*/
2039 int gs_gamenum;
2040 char gs_kind[MSG_SIZ];
2041 static char player1Name[128] = "";
2042 static char player2Name[128] = "";
2043 static int player1Rating = -1;
2044 static int player2Rating = -1;
2045 /*----------------------------*/
2046
2047 ColorClass curColor = ColorNormal;
2048 int suppressKibitz = 0;
2049
2050 void
2051 read_from_ics(isr, closure, data, count, error)
2052      InputSourceRef isr;
2053      VOIDSTAR closure;
2054      char *data;
2055      int count;
2056      int error;
2057 {
2058 #define BUF_SIZE 8192
2059 #define STARTED_NONE 0
2060 #define STARTED_MOVES 1
2061 #define STARTED_BOARD 2
2062 #define STARTED_OBSERVE 3
2063 #define STARTED_HOLDINGS 4
2064 #define STARTED_CHATTER 5
2065 #define STARTED_COMMENT 6
2066 #define STARTED_MOVES_NOHIDE 7
2067     
2068     static int started = STARTED_NONE;
2069     static char parse[20000];
2070     static int parse_pos = 0;
2071     static char buf[BUF_SIZE + 1];
2072     static int firstTime = TRUE, intfSet = FALSE;
2073     static ColorClass prevColor = ColorNormal;
2074     static int savingComment = FALSE;
2075     char str[500];
2076     int i, oldi;
2077     int buf_len;
2078     int next_out;
2079     int tkind;
2080     int backup;    /* [DM] For zippy color lines */
2081     char *p;
2082
2083     if (appData.debugMode) {
2084       if (!error) {
2085         fprintf(debugFP, "<ICS: ");
2086         show_bytes(debugFP, data, count);
2087         fprintf(debugFP, "\n");
2088       }
2089     }
2090
2091     if (appData.debugMode) { int f = forwardMostMove;
2092         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2093                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2094     }
2095     if (count > 0) {
2096         /* If last read ended with a partial line that we couldn't parse,
2097            prepend it to the new read and try again. */
2098         if (leftover_len > 0) {
2099             for (i=0; i<leftover_len; i++)
2100               buf[i] = buf[leftover_start + i];
2101         }
2102
2103         /* Copy in new characters, removing nulls and \r's */
2104         buf_len = leftover_len;
2105         for (i = 0; i < count; i++) {
2106             if (data[i] != NULLCHAR && data[i] != '\r')
2107               buf[buf_len++] = data[i];
2108             if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && 
2109                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') 
2110                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2111         }
2112
2113         buf[buf_len] = NULLCHAR;
2114         next_out = leftover_len;
2115         leftover_start = 0;
2116         
2117         i = 0;
2118         while (i < buf_len) {
2119             /* Deal with part of the TELNET option negotiation
2120                protocol.  We refuse to do anything beyond the
2121                defaults, except that we allow the WILL ECHO option,
2122                which ICS uses to turn off password echoing when we are
2123                directly connected to it.  We reject this option
2124                if localLineEditing mode is on (always on in xboard)
2125                and we are talking to port 23, which might be a real
2126                telnet server that will try to keep WILL ECHO on permanently.
2127              */
2128             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2129                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2130                 unsigned char option;
2131                 oldi = i;
2132                 switch ((unsigned char) buf[++i]) {
2133                   case TN_WILL:
2134                     if (appData.debugMode)
2135                       fprintf(debugFP, "\n<WILL ");
2136                     switch (option = (unsigned char) buf[++i]) {
2137                       case TN_ECHO:
2138                         if (appData.debugMode)
2139                           fprintf(debugFP, "ECHO ");
2140                         /* Reply only if this is a change, according
2141                            to the protocol rules. */
2142                         if (remoteEchoOption) break;
2143                         if (appData.localLineEditing &&
2144                             atoi(appData.icsPort) == TN_PORT) {
2145                             TelnetRequest(TN_DONT, TN_ECHO);
2146                         } else {
2147                             EchoOff();
2148                             TelnetRequest(TN_DO, TN_ECHO);
2149                             remoteEchoOption = TRUE;
2150                         }
2151                         break;
2152                       default:
2153                         if (appData.debugMode)
2154                           fprintf(debugFP, "%d ", option);
2155                         /* Whatever this is, we don't want it. */
2156                         TelnetRequest(TN_DONT, option);
2157                         break;
2158                     }
2159                     break;
2160                   case TN_WONT:
2161                     if (appData.debugMode)
2162                       fprintf(debugFP, "\n<WONT ");
2163                     switch (option = (unsigned char) buf[++i]) {
2164                       case TN_ECHO:
2165                         if (appData.debugMode)
2166                           fprintf(debugFP, "ECHO ");
2167                         /* Reply only if this is a change, according
2168                            to the protocol rules. */
2169                         if (!remoteEchoOption) break;
2170                         EchoOn();
2171                         TelnetRequest(TN_DONT, TN_ECHO);
2172                         remoteEchoOption = FALSE;
2173                         break;
2174                       default:
2175                         if (appData.debugMode)
2176                           fprintf(debugFP, "%d ", (unsigned char) option);
2177                         /* Whatever this is, it must already be turned
2178                            off, because we never agree to turn on
2179                            anything non-default, so according to the
2180                            protocol rules, we don't reply. */
2181                         break;
2182                     }
2183                     break;
2184                   case TN_DO:
2185                     if (appData.debugMode)
2186                       fprintf(debugFP, "\n<DO ");
2187                     switch (option = (unsigned char) buf[++i]) {
2188                       default:
2189                         /* Whatever this is, we refuse to do it. */
2190                         if (appData.debugMode)
2191                           fprintf(debugFP, "%d ", option);
2192                         TelnetRequest(TN_WONT, option);
2193                         break;
2194                     }
2195                     break;
2196                   case TN_DONT:
2197                     if (appData.debugMode)
2198                       fprintf(debugFP, "\n<DONT ");
2199                     switch (option = (unsigned char) buf[++i]) {
2200                       default:
2201                         if (appData.debugMode)
2202                           fprintf(debugFP, "%d ", option);
2203                         /* Whatever this is, we are already not doing
2204                            it, because we never agree to do anything
2205                            non-default, so according to the protocol
2206                            rules, we don't reply. */
2207                         break;
2208                     }
2209                     break;
2210                   case TN_IAC:
2211                     if (appData.debugMode)
2212                       fprintf(debugFP, "\n<IAC ");
2213                     /* Doubled IAC; pass it through */
2214                     i--;
2215                     break;
2216                   default:
2217                     if (appData.debugMode)
2218                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2219                     /* Drop all other telnet commands on the floor */
2220                     break;
2221                 }
2222                 if (oldi > next_out)
2223                   SendToPlayer(&buf[next_out], oldi - next_out);
2224                 if (++i > next_out)
2225                   next_out = i;
2226                 continue;
2227             }
2228                 
2229             /* OK, this at least will *usually* work */
2230             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2231                 loggedOn = TRUE;
2232             }
2233             
2234             if (loggedOn && !intfSet) {
2235                 if (ics_type == ICS_ICC) {
2236                   sprintf(str,
2237                           "/set-quietly interface %s\n/set-quietly style 12\n",
2238                           programVersion);
2239
2240                 } else if (ics_type == ICS_CHESSNET) {
2241                   sprintf(str, "/style 12\n");
2242                 } else {
2243                   strcpy(str, "alias $ @\n$set interface ");
2244                   strcat(str, programVersion);
2245                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2246 #ifdef WIN32
2247                   strcat(str, "$iset nohighlight 1\n");
2248 #endif
2249                   strcat(str, "$iset lock 1\n$style 12\n");
2250                 }
2251                 SendToICS(str);
2252                 intfSet = TRUE;
2253             }
2254
2255             if (started == STARTED_COMMENT) {
2256                 /* Accumulate characters in comment */
2257                 parse[parse_pos++] = buf[i];
2258                 if (buf[i] == '\n') {
2259                     parse[parse_pos] = NULLCHAR;
2260                     if(!suppressKibitz) // [HGM] kibitz
2261                         AppendComment(forwardMostMove, StripHighlight(parse));
2262                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2263                         int nrDigit = 0, nrAlph = 0, i;
2264                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2265                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2266                         parse[parse_pos] = NULLCHAR;
2267                         // try to be smart: if it does not look like search info, it should go to
2268                         // ICS interaction window after all, not to engine-output window.
2269                         for(i=0; i<parse_pos; i++) { // count letters and digits
2270                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2271                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2272                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2273                         }
2274                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2275                             int depth=0; float score;
2276                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2277                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2278                                 pvInfoList[forwardMostMove-1].depth = depth;
2279                                 pvInfoList[forwardMostMove-1].score = 100*score;
2280                             }
2281                             OutputKibitz(suppressKibitz, parse);
2282                         } else {
2283                             char tmp[MSG_SIZ];
2284                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2285                             SendToPlayer(tmp, strlen(tmp));
2286                         }
2287                     }
2288                     started = STARTED_NONE;
2289                 } else {
2290                     /* Don't match patterns against characters in chatter */
2291                     i++;
2292                     continue;
2293                 }
2294             }
2295             if (started == STARTED_CHATTER) {
2296                 if (buf[i] != '\n') {
2297                     /* Don't match patterns against characters in chatter */
2298                     i++;
2299                     continue;
2300                 }
2301                 started = STARTED_NONE;
2302             }
2303
2304             /* Kludge to deal with rcmd protocol */
2305             if (firstTime && looking_at(buf, &i, "\001*")) {
2306                 DisplayFatalError(&buf[1], 0, 1);
2307                 continue;
2308             } else {
2309                 firstTime = FALSE;
2310             }
2311
2312             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2313                 ics_type = ICS_ICC;
2314                 ics_prefix = "/";
2315                 if (appData.debugMode)
2316                   fprintf(debugFP, "ics_type %d\n", ics_type);
2317                 continue;
2318             }
2319             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2320                 ics_type = ICS_FICS;
2321                 ics_prefix = "$";
2322                 if (appData.debugMode)
2323                   fprintf(debugFP, "ics_type %d\n", ics_type);
2324                 continue;
2325             }
2326             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2327                 ics_type = ICS_CHESSNET;
2328                 ics_prefix = "/";
2329                 if (appData.debugMode)
2330                   fprintf(debugFP, "ics_type %d\n", ics_type);
2331                 continue;
2332             }
2333
2334             if (!loggedOn &&
2335                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2336                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2337                  looking_at(buf, &i, "will be \"*\""))) {
2338               strcpy(ics_handle, star_match[0]);
2339               continue;
2340             }
2341
2342             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2343               char buf[MSG_SIZ];
2344               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2345               DisplayIcsInteractionTitle(buf);
2346               have_set_title = TRUE;
2347             }
2348
2349             /* skip finger notes */
2350             if (started == STARTED_NONE &&
2351                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2352                  (buf[i] == '1' && buf[i+1] == '0')) &&
2353                 buf[i+2] == ':' && buf[i+3] == ' ') {
2354               started = STARTED_CHATTER;
2355               i += 3;
2356               continue;
2357             }
2358
2359             /* skip formula vars */
2360             if (started == STARTED_NONE &&
2361                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2362               started = STARTED_CHATTER;
2363               i += 3;
2364               continue;
2365             }
2366
2367             oldi = i;
2368             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2369             if (appData.autoKibitz && started == STARTED_NONE && 
2370                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2371                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2372                 if(looking_at(buf, &i, "* kibitzes: ") &&
2373                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2374                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2375                         suppressKibitz = TRUE;
2376                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2377                                 && (gameMode == IcsPlayingWhite)) ||
2378                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2379                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2380                             started = STARTED_CHATTER; // own kibitz we simply discard
2381                         else {
2382                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2383                             parse_pos = 0; parse[0] = NULLCHAR;
2384                             savingComment = TRUE;
2385                             suppressKibitz = gameMode != IcsObserving ? 2 :
2386                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2387                         } 
2388                         continue;
2389                 } else
2390                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2391                     started = STARTED_CHATTER;
2392                     suppressKibitz = TRUE;
2393                 }
2394             } // [HGM] kibitz: end of patch
2395
2396             if (appData.zippyTalk || appData.zippyPlay) {
2397                 /* [DM] Backup address for color zippy lines */
2398                 backup = i;
2399 #if ZIPPY
2400        #ifdef WIN32
2401                if (loggedOn == TRUE)
2402                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2403                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2404        #else
2405                 if (ZippyControl(buf, &i) ||
2406                     ZippyConverse(buf, &i) ||
2407                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2408                       loggedOn = TRUE;
2409                       if (!appData.colorize) continue;
2410                 }
2411        #endif
2412 #endif
2413             } // [DM] 'else { ' deleted
2414                 if (/* Don't color "message" or "messages" output */
2415                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2416                     looking_at(buf, &i, "*. * at *:*: ") ||
2417                     looking_at(buf, &i, "--* (*:*): ") ||
2418                     /* Regular tells and says */
2419                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2420                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2421                     looking_at(buf, &i, "* says: ") ||
2422                     /* Message notifications (same color as tells) */
2423                     looking_at(buf, &i, "* has left a message ") ||
2424                     looking_at(buf, &i, "* just sent you a message:\n") ||
2425                     /* Whispers and kibitzes */
2426                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2427                     looking_at(buf, &i, "* kibitzes: ") ||
2428                     /* Channel tells */
2429                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2430
2431                   if (tkind == 1 && strchr(star_match[0], ':')) {
2432                       /* Avoid "tells you:" spoofs in channels */
2433                      tkind = 3;
2434                   }
2435                   if (star_match[0][0] == NULLCHAR ||
2436                       strchr(star_match[0], ' ') ||
2437                       (tkind == 3 && strchr(star_match[1], ' '))) {
2438                     /* Reject bogus matches */
2439                     i = oldi;
2440                   } else {
2441                     if (appData.colorize) {
2442                       if (oldi > next_out) {
2443                         SendToPlayer(&buf[next_out], oldi - next_out);
2444                         next_out = oldi;
2445                       }
2446                       switch (tkind) {
2447                       case 1:
2448                         Colorize(ColorTell, FALSE);
2449                         curColor = ColorTell;
2450                         break;
2451                       case 2:
2452                         Colorize(ColorKibitz, FALSE);
2453                         curColor = ColorKibitz;
2454                         break;
2455                       case 3:
2456                         p = strrchr(star_match[1], '(');
2457                         if (p == NULL) {
2458                           p = star_match[1];
2459                         } else {
2460                           p++;
2461                         }
2462                         if (atoi(p) == 1) {
2463                           Colorize(ColorChannel1, FALSE);
2464                           curColor = ColorChannel1;
2465                         } else {
2466                           Colorize(ColorChannel, FALSE);
2467                           curColor = ColorChannel;
2468                         }
2469                         break;
2470                       case 5:
2471                         curColor = ColorNormal;
2472                         break;
2473                       }
2474                     }
2475                     if (started == STARTED_NONE && appData.autoComment &&
2476                         (gameMode == IcsObserving ||
2477                          gameMode == IcsPlayingWhite ||
2478                          gameMode == IcsPlayingBlack)) {
2479                       parse_pos = i - oldi;
2480                       memcpy(parse, &buf[oldi], parse_pos);
2481                       parse[parse_pos] = NULLCHAR;
2482                       started = STARTED_COMMENT;
2483                       savingComment = TRUE;
2484                     } else {
2485                       started = STARTED_CHATTER;
2486                       savingComment = FALSE;
2487                     }
2488                     loggedOn = TRUE;
2489                     continue;
2490                   }
2491                 }
2492
2493                 if (looking_at(buf, &i, "* s-shouts: ") ||
2494                     looking_at(buf, &i, "* c-shouts: ")) {
2495                     if (appData.colorize) {
2496                         if (oldi > next_out) {
2497                             SendToPlayer(&buf[next_out], oldi - next_out);
2498                             next_out = oldi;
2499                         }
2500                         Colorize(ColorSShout, FALSE);
2501                         curColor = ColorSShout;
2502                     }
2503                     loggedOn = TRUE;
2504                     started = STARTED_CHATTER;
2505                     continue;
2506                 }
2507
2508                 if (looking_at(buf, &i, "--->")) {
2509                     loggedOn = TRUE;
2510                     continue;
2511                 }
2512
2513                 if (looking_at(buf, &i, "* shouts: ") ||
2514                     looking_at(buf, &i, "--> ")) {
2515                     if (appData.colorize) {
2516                         if (oldi > next_out) {
2517                             SendToPlayer(&buf[next_out], oldi - next_out);
2518                             next_out = oldi;
2519                         }
2520                         Colorize(ColorShout, FALSE);
2521                         curColor = ColorShout;
2522                     }
2523                     loggedOn = TRUE;
2524                     started = STARTED_CHATTER;
2525                     continue;
2526                 }
2527
2528                 if (looking_at( buf, &i, "Challenge:")) {
2529                     if (appData.colorize) {
2530                         if (oldi > next_out) {
2531                             SendToPlayer(&buf[next_out], oldi - next_out);
2532                             next_out = oldi;
2533                         }
2534                         Colorize(ColorChallenge, FALSE);
2535                         curColor = ColorChallenge;
2536                     }
2537                     loggedOn = TRUE;
2538                     continue;
2539                 }
2540
2541                 if (looking_at(buf, &i, "* offers you") ||
2542                     looking_at(buf, &i, "* offers to be") ||
2543                     looking_at(buf, &i, "* would like to") ||
2544                     looking_at(buf, &i, "* requests to") ||
2545                     looking_at(buf, &i, "Your opponent offers") ||
2546                     looking_at(buf, &i, "Your opponent requests")) {
2547
2548                     if (appData.colorize) {
2549                         if (oldi > next_out) {
2550                             SendToPlayer(&buf[next_out], oldi - next_out);
2551                             next_out = oldi;
2552                         }
2553                         Colorize(ColorRequest, FALSE);
2554                         curColor = ColorRequest;
2555                     }
2556                     continue;
2557                 }
2558
2559                 if (looking_at(buf, &i, "* (*) seeking")) {
2560                     if (appData.colorize) {
2561                         if (oldi > next_out) {
2562                             SendToPlayer(&buf[next_out], oldi - next_out);
2563                             next_out = oldi;
2564                         }
2565                         Colorize(ColorSeek, FALSE);
2566                         curColor = ColorSeek;
2567                     }
2568                     continue;
2569             }
2570
2571             if (looking_at(buf, &i, "\\   ")) {
2572                 if (prevColor != ColorNormal) {
2573                     if (oldi > next_out) {
2574                         SendToPlayer(&buf[next_out], oldi - next_out);
2575                         next_out = oldi;
2576                     }
2577                     Colorize(prevColor, TRUE);
2578                     curColor = prevColor;
2579                 }
2580                 if (savingComment) {
2581                     parse_pos = i - oldi;
2582                     memcpy(parse, &buf[oldi], parse_pos);
2583                     parse[parse_pos] = NULLCHAR;
2584                     started = STARTED_COMMENT;
2585                 } else {
2586                     started = STARTED_CHATTER;
2587                 }
2588                 continue;
2589             }
2590
2591             if (looking_at(buf, &i, "Black Strength :") ||
2592                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2593                 looking_at(buf, &i, "<10>") ||
2594                 looking_at(buf, &i, "#@#")) {
2595                 /* Wrong board style */
2596                 loggedOn = TRUE;
2597                 SendToICS(ics_prefix);
2598                 SendToICS("set style 12\n");
2599                 SendToICS(ics_prefix);
2600                 SendToICS("refresh\n");
2601                 continue;
2602             }
2603             
2604             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2605                 ICSInitScript();
2606                 have_sent_ICS_logon = 1;
2607                 continue;
2608             }
2609               
2610             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2611                 (looking_at(buf, &i, "\n<12> ") ||
2612                  looking_at(buf, &i, "<12> "))) {
2613                 loggedOn = TRUE;
2614                 if (oldi > next_out) {
2615                     SendToPlayer(&buf[next_out], oldi - next_out);
2616                 }
2617                 next_out = i;
2618                 started = STARTED_BOARD;
2619                 parse_pos = 0;
2620                 continue;
2621             }
2622
2623             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2624                 looking_at(buf, &i, "<b1> ")) {
2625                 if (oldi > next_out) {
2626                     SendToPlayer(&buf[next_out], oldi - next_out);
2627                 }
2628                 next_out = i;
2629                 started = STARTED_HOLDINGS;
2630                 parse_pos = 0;
2631                 continue;
2632             }
2633
2634             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2635                 loggedOn = TRUE;
2636                 /* Header for a move list -- first line */
2637
2638                 switch (ics_getting_history) {
2639                   case H_FALSE:
2640                     switch (gameMode) {
2641                       case IcsIdle:
2642                       case BeginningOfGame:
2643                         /* User typed "moves" or "oldmoves" while we
2644                            were idle.  Pretend we asked for these
2645                            moves and soak them up so user can step
2646                            through them and/or save them.
2647                            */
2648                         Reset(FALSE, TRUE);
2649                         gameMode = IcsObserving;
2650                         ModeHighlight();
2651                         ics_gamenum = -1;
2652                         ics_getting_history = H_GOT_UNREQ_HEADER;
2653                         break;
2654                       case EditGame: /*?*/
2655                       case EditPosition: /*?*/
2656                         /* Should above feature work in these modes too? */
2657                         /* For now it doesn't */
2658                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2659                         break;
2660                       default:
2661                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2662                         break;
2663                     }
2664                     break;
2665                   case H_REQUESTED:
2666                     /* Is this the right one? */
2667                     if (gameInfo.white && gameInfo.black &&
2668                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2669                         strcmp(gameInfo.black, star_match[2]) == 0) {
2670                         /* All is well */
2671                         ics_getting_history = H_GOT_REQ_HEADER;
2672                     }
2673                     break;
2674                   case H_GOT_REQ_HEADER:
2675                   case H_GOT_UNREQ_HEADER:
2676                   case H_GOT_UNWANTED_HEADER:
2677                   case H_GETTING_MOVES:
2678                     /* Should not happen */
2679                     DisplayError(_("Error gathering move list: two headers"), 0);
2680                     ics_getting_history = H_FALSE;
2681                     break;
2682                 }
2683
2684                 /* Save player ratings into gameInfo if needed */
2685                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2686                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2687                     (gameInfo.whiteRating == -1 ||
2688                      gameInfo.blackRating == -1)) {
2689
2690                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2691                     gameInfo.blackRating = string_to_rating(star_match[3]);
2692                     if (appData.debugMode)
2693                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2694                               gameInfo.whiteRating, gameInfo.blackRating);
2695                 }
2696                 continue;
2697             }
2698
2699             if (looking_at(buf, &i,
2700               "* * match, initial time: * minute*, increment: * second")) {
2701                 /* Header for a move list -- second line */
2702                 /* Initial board will follow if this is a wild game */
2703                 if (gameInfo.event != NULL) free(gameInfo.event);
2704                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2705                 gameInfo.event = StrSave(str);
2706                 /* [HGM] we switched variant. Translate boards if needed. */
2707                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2708                 continue;
2709             }
2710
2711             if (looking_at(buf, &i, "Move  ")) {
2712                 /* Beginning of a move list */
2713                 switch (ics_getting_history) {
2714                   case H_FALSE:
2715                     /* Normally should not happen */
2716                     /* Maybe user hit reset while we were parsing */
2717                     break;
2718                   case H_REQUESTED:
2719                     /* Happens if we are ignoring a move list that is not
2720                      * the one we just requested.  Common if the user
2721                      * tries to observe two games without turning off
2722                      * getMoveList */
2723                     break;
2724                   case H_GETTING_MOVES:
2725                     /* Should not happen */
2726                     DisplayError(_("Error gathering move list: nested"), 0);
2727                     ics_getting_history = H_FALSE;
2728                     break;
2729                   case H_GOT_REQ_HEADER:
2730                     ics_getting_history = H_GETTING_MOVES;
2731                     started = STARTED_MOVES;
2732                     parse_pos = 0;
2733                     if (oldi > next_out) {
2734                         SendToPlayer(&buf[next_out], oldi - next_out);
2735                     }
2736                     break;
2737                   case H_GOT_UNREQ_HEADER:
2738                     ics_getting_history = H_GETTING_MOVES;
2739                     started = STARTED_MOVES_NOHIDE;
2740                     parse_pos = 0;
2741                     break;
2742                   case H_GOT_UNWANTED_HEADER:
2743                     ics_getting_history = H_FALSE;
2744                     break;
2745                 }
2746                 continue;
2747             }                           
2748             
2749             if (looking_at(buf, &i, "% ") ||
2750                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2751                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2752                 savingComment = FALSE;
2753                 switch (started) {
2754                   case STARTED_MOVES:
2755                   case STARTED_MOVES_NOHIDE:
2756                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2757                     parse[parse_pos + i - oldi] = NULLCHAR;
2758                     ParseGameHistory(parse);
2759 #if ZIPPY
2760                     if (appData.zippyPlay && first.initDone) {
2761                         FeedMovesToProgram(&first, forwardMostMove);
2762                         if (gameMode == IcsPlayingWhite) {
2763                             if (WhiteOnMove(forwardMostMove)) {
2764                                 if (first.sendTime) {
2765                                   if (first.useColors) {
2766                                     SendToProgram("black\n", &first); 
2767                                   }
2768                                   SendTimeRemaining(&first, TRUE);
2769                                 }
2770 #if 0
2771                                 if (first.useColors) {
2772                                   SendToProgram("white\ngo\n", &first);
2773                                 } else {
2774                                   SendToProgram("go\n", &first);
2775                                 }
2776 #else
2777                                 if (first.useColors) {
2778                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2779                                 }
2780                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2781 #endif
2782                                 first.maybeThinking = TRUE;
2783                             } else {
2784                                 if (first.usePlayother) {
2785                                   if (first.sendTime) {
2786                                     SendTimeRemaining(&first, TRUE);
2787                                   }
2788                                   SendToProgram("playother\n", &first);
2789                                   firstMove = FALSE;
2790                                 } else {
2791                                   firstMove = TRUE;
2792                                 }
2793                             }
2794                         } else if (gameMode == IcsPlayingBlack) {
2795                             if (!WhiteOnMove(forwardMostMove)) {
2796                                 if (first.sendTime) {
2797                                   if (first.useColors) {
2798                                     SendToProgram("white\n", &first);
2799                                   }
2800                                   SendTimeRemaining(&first, FALSE);
2801                                 }
2802 #if 0
2803                                 if (first.useColors) {
2804                                   SendToProgram("black\ngo\n", &first);
2805                                 } else {
2806                                   SendToProgram("go\n", &first);
2807                                 }
2808 #else
2809                                 if (first.useColors) {
2810                                   SendToProgram("black\n", &first);
2811                                 }
2812                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2813 #endif
2814                                 first.maybeThinking = TRUE;
2815                             } else {
2816                                 if (first.usePlayother) {
2817                                   if (first.sendTime) {
2818                                     SendTimeRemaining(&first, FALSE);
2819                                   }
2820                                   SendToProgram("playother\n", &first);
2821                                   firstMove = FALSE;
2822                                 } else {
2823                                   firstMove = TRUE;
2824                                 }
2825                             }
2826                         }                       
2827                     }
2828 #endif
2829                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2830                         /* Moves came from oldmoves or moves command
2831                            while we weren't doing anything else.
2832                            */
2833                         currentMove = forwardMostMove;
2834                         ClearHighlights();/*!!could figure this out*/
2835                         flipView = appData.flipView;
2836                         DrawPosition(FALSE, boards[currentMove]);
2837                         DisplayBothClocks();
2838                         sprintf(str, "%s vs. %s",
2839                                 gameInfo.white, gameInfo.black);
2840                         DisplayTitle(str);
2841                         gameMode = IcsIdle;
2842                     } else {
2843                         /* Moves were history of an active game */
2844                         if (gameInfo.resultDetails != NULL) {
2845                             free(gameInfo.resultDetails);
2846                             gameInfo.resultDetails = NULL;
2847                         }
2848                     }
2849                     HistorySet(parseList, backwardMostMove,
2850                                forwardMostMove, currentMove-1);
2851                     DisplayMove(currentMove - 1);
2852                     if (started == STARTED_MOVES) next_out = i;
2853                     started = STARTED_NONE;
2854                     ics_getting_history = H_FALSE;
2855                     break;
2856
2857                   case STARTED_OBSERVE:
2858                     started = STARTED_NONE;
2859                     SendToICS(ics_prefix);
2860                     SendToICS("refresh\n");
2861                     break;
2862
2863                   default:
2864                     break;
2865                 }
2866                 if(bookHit) { // [HGM] book: simulate book reply
2867                     static char bookMove[MSG_SIZ]; // a bit generous?
2868
2869                     programStats.nodes = programStats.depth = programStats.time = 
2870                     programStats.score = programStats.got_only_move = 0;
2871                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2872
2873                     strcpy(bookMove, "move ");
2874                     strcat(bookMove, bookHit);
2875                     HandleMachineMove(bookMove, &first);
2876                 }
2877                 continue;
2878             }
2879             
2880             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2881                  started == STARTED_HOLDINGS ||
2882                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2883                 /* Accumulate characters in move list or board */
2884                 parse[parse_pos++] = buf[i];
2885             }
2886             
2887             /* Start of game messages.  Mostly we detect start of game
2888                when the first board image arrives.  On some versions
2889                of the ICS, though, we need to do a "refresh" after starting
2890                to observe in order to get the current board right away. */
2891             if (looking_at(buf, &i, "Adding game * to observation list")) {
2892                 started = STARTED_OBSERVE;
2893                 continue;
2894             }
2895
2896             /* Handle auto-observe */
2897             if (appData.autoObserve &&
2898                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2899                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2900                 char *player;
2901                 /* Choose the player that was highlighted, if any. */
2902                 if (star_match[0][0] == '\033' ||
2903                     star_match[1][0] != '\033') {
2904                     player = star_match[0];
2905                 } else {
2906                     player = star_match[2];
2907                 }
2908                 sprintf(str, "%sobserve %s\n",
2909                         ics_prefix, StripHighlightAndTitle(player));
2910                 SendToICS(str);
2911
2912                 /* Save ratings from notify string */
2913                 strcpy(player1Name, star_match[0]);
2914                 player1Rating = string_to_rating(star_match[1]);
2915                 strcpy(player2Name, star_match[2]);
2916                 player2Rating = string_to_rating(star_match[3]);
2917
2918                 if (appData.debugMode)
2919                   fprintf(debugFP, 
2920                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2921                           player1Name, player1Rating,
2922                           player2Name, player2Rating);
2923
2924                 continue;
2925             }
2926
2927             /* Deal with automatic examine mode after a game,
2928                and with IcsObserving -> IcsExamining transition */
2929             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2930                 looking_at(buf, &i, "has made you an examiner of game *")) {
2931
2932                 int gamenum = atoi(star_match[0]);
2933                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2934                     gamenum == ics_gamenum) {
2935                     /* We were already playing or observing this game;
2936                        no need to refetch history */
2937                     gameMode = IcsExamining;
2938                     if (pausing) {
2939                         pauseExamForwardMostMove = forwardMostMove;
2940                     } else if (currentMove < forwardMostMove) {
2941                         ForwardInner(forwardMostMove);
2942                     }
2943                 } else {
2944                     /* I don't think this case really can happen */
2945                     SendToICS(ics_prefix);
2946                     SendToICS("refresh\n");
2947                 }
2948                 continue;
2949             }    
2950             
2951             /* Error messages */
2952 //          if (ics_user_moved) {
2953             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2954                 if (looking_at(buf, &i, "Illegal move") ||
2955                     looking_at(buf, &i, "Not a legal move") ||
2956                     looking_at(buf, &i, "Your king is in check") ||
2957                     looking_at(buf, &i, "It isn't your turn") ||
2958                     looking_at(buf, &i, "It is not your move")) {
2959                     /* Illegal move */
2960                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2961                         currentMove = --forwardMostMove;
2962                         DisplayMove(currentMove - 1); /* before DMError */
2963                         DrawPosition(FALSE, boards[currentMove]);
2964                         SwitchClocks();
2965                         DisplayBothClocks();
2966                     }
2967                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2968                     ics_user_moved = 0;
2969                     continue;
2970                 }
2971             }
2972
2973             if (looking_at(buf, &i, "still have time") ||
2974                 looking_at(buf, &i, "not out of time") ||
2975                 looking_at(buf, &i, "either player is out of time") ||
2976                 looking_at(buf, &i, "has timeseal; checking")) {
2977                 /* We must have called his flag a little too soon */
2978                 whiteFlag = blackFlag = FALSE;
2979                 continue;
2980             }
2981
2982             if (looking_at(buf, &i, "added * seconds to") ||
2983                 looking_at(buf, &i, "seconds were added to")) {
2984                 /* Update the clocks */
2985                 SendToICS(ics_prefix);
2986                 SendToICS("refresh\n");
2987                 continue;
2988             }
2989
2990             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2991                 ics_clock_paused = TRUE;
2992                 StopClocks();
2993                 continue;
2994             }
2995
2996             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
2997                 ics_clock_paused = FALSE;
2998                 StartClocks();
2999                 continue;
3000             }
3001
3002             /* Grab player ratings from the Creating: message.
3003                Note we have to check for the special case when
3004                the ICS inserts things like [white] or [black]. */
3005             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3006                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3007                 /* star_matches:
3008                    0    player 1 name (not necessarily white)
3009                    1    player 1 rating
3010                    2    empty, white, or black (IGNORED)
3011                    3    player 2 name (not necessarily black)
3012                    4    player 2 rating
3013                    
3014                    The names/ratings are sorted out when the game
3015                    actually starts (below).
3016                 */
3017                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3018                 player1Rating = string_to_rating(star_match[1]);
3019                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3020                 player2Rating = string_to_rating(star_match[4]);
3021
3022                 if (appData.debugMode)
3023                   fprintf(debugFP, 
3024                           "Ratings from 'Creating:' %s %d, %s %d\n",
3025                           player1Name, player1Rating,
3026                           player2Name, player2Rating);
3027
3028                 continue;
3029             }
3030             
3031             /* Improved generic start/end-of-game messages */
3032             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3033                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3034                 /* If tkind == 0: */
3035                 /* star_match[0] is the game number */
3036                 /*           [1] is the white player's name */
3037                 /*           [2] is the black player's name */
3038                 /* For end-of-game: */
3039                 /*           [3] is the reason for the game end */
3040                 /*           [4] is a PGN end game-token, preceded by " " */
3041                 /* For start-of-game: */
3042                 /*           [3] begins with "Creating" or "Continuing" */
3043                 /*           [4] is " *" or empty (don't care). */
3044                 int gamenum = atoi(star_match[0]);
3045                 char *whitename, *blackname, *why, *endtoken;
3046                 ChessMove endtype = (ChessMove) 0;
3047
3048                 if (tkind == 0) {
3049                   whitename = star_match[1];
3050                   blackname = star_match[2];
3051                   why = star_match[3];
3052                   endtoken = star_match[4];
3053                 } else {
3054                   whitename = star_match[1];
3055                   blackname = star_match[3];
3056                   why = star_match[5];
3057                   endtoken = star_match[6];
3058                 }
3059
3060                 /* Game start messages */
3061                 if (strncmp(why, "Creating ", 9) == 0 ||
3062                     strncmp(why, "Continuing ", 11) == 0) {
3063                     gs_gamenum = gamenum;
3064                     strcpy(gs_kind, strchr(why, ' ') + 1);
3065 #if ZIPPY
3066                     if (appData.zippyPlay) {
3067                         ZippyGameStart(whitename, blackname);
3068                     }
3069 #endif /*ZIPPY*/
3070                     continue;
3071                 }
3072
3073                 /* Game end messages */
3074                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3075                     ics_gamenum != gamenum) {
3076                     continue;
3077                 }
3078                 while (endtoken[0] == ' ') endtoken++;
3079                 switch (endtoken[0]) {
3080                   case '*':
3081                   default:
3082                     endtype = GameUnfinished;
3083                     break;
3084                   case '0':
3085                     endtype = BlackWins;
3086                     break;
3087                   case '1':
3088                     if (endtoken[1] == '/')
3089                       endtype = GameIsDrawn;
3090                     else
3091                       endtype = WhiteWins;
3092                     break;
3093                 }
3094                 GameEnds(endtype, why, GE_ICS);
3095 #if ZIPPY
3096                 if (appData.zippyPlay && first.initDone) {
3097                     ZippyGameEnd(endtype, why);
3098                     if (first.pr == NULL) {
3099                       /* Start the next process early so that we'll
3100                          be ready for the next challenge */
3101                       StartChessProgram(&first);
3102                     }
3103                     /* Send "new" early, in case this command takes
3104                        a long time to finish, so that we'll be ready
3105                        for the next challenge. */
3106                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3107                     Reset(TRUE, TRUE);
3108                 }
3109 #endif /*ZIPPY*/
3110                 continue;
3111             }
3112
3113             if (looking_at(buf, &i, "Removing game * from observation") ||
3114                 looking_at(buf, &i, "no longer observing game *") ||
3115                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3116                 if (gameMode == IcsObserving &&
3117                     atoi(star_match[0]) == ics_gamenum)
3118                   {
3119                       /* icsEngineAnalyze */
3120                       if (appData.icsEngineAnalyze) {
3121                             ExitAnalyzeMode();
3122                             ModeHighlight();
3123                       }
3124                       StopClocks();
3125                       gameMode = IcsIdle;
3126                       ics_gamenum = -1;
3127                       ics_user_moved = FALSE;
3128                   }
3129                 continue;
3130             }
3131
3132             if (looking_at(buf, &i, "no longer examining game *")) {
3133                 if (gameMode == IcsExamining &&
3134                     atoi(star_match[0]) == ics_gamenum)
3135                   {
3136                       gameMode = IcsIdle;
3137                       ics_gamenum = -1;
3138                       ics_user_moved = FALSE;
3139                   }
3140                 continue;
3141             }
3142
3143             /* Advance leftover_start past any newlines we find,
3144                so only partial lines can get reparsed */
3145             if (looking_at(buf, &i, "\n")) {
3146                 prevColor = curColor;
3147                 if (curColor != ColorNormal) {
3148                     if (oldi > next_out) {
3149                         SendToPlayer(&buf[next_out], oldi - next_out);
3150                         next_out = oldi;
3151                     }
3152                     Colorize(ColorNormal, FALSE);
3153                     curColor = ColorNormal;
3154                 }
3155                 if (started == STARTED_BOARD) {
3156                     started = STARTED_NONE;
3157                     parse[parse_pos] = NULLCHAR;
3158                     ParseBoard12(parse);
3159                     ics_user_moved = 0;
3160
3161                     /* Send premove here */
3162                     if (appData.premove) {
3163                       char str[MSG_SIZ];
3164                       if (currentMove == 0 &&
3165                           gameMode == IcsPlayingWhite &&
3166                           appData.premoveWhite) {
3167                         sprintf(str, "%s%s\n", ics_prefix,
3168                                 appData.premoveWhiteText);
3169                         if (appData.debugMode)
3170                           fprintf(debugFP, "Sending premove:\n");
3171                         SendToICS(str);
3172                       } else if (currentMove == 1 &&
3173                                  gameMode == IcsPlayingBlack &&
3174                                  appData.premoveBlack) {
3175                         sprintf(str, "%s%s\n", ics_prefix,
3176                                 appData.premoveBlackText);
3177                         if (appData.debugMode)
3178                           fprintf(debugFP, "Sending premove:\n");
3179                         SendToICS(str);
3180                       } else if (gotPremove) {
3181                         gotPremove = 0;
3182                         ClearPremoveHighlights();
3183                         if (appData.debugMode)
3184                           fprintf(debugFP, "Sending premove:\n");
3185                           UserMoveEvent(premoveFromX, premoveFromY, 
3186                                         premoveToX, premoveToY, 
3187                                         premovePromoChar);
3188                       }
3189                     }
3190
3191                     /* Usually suppress following prompt */
3192                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3193                         if (looking_at(buf, &i, "*% ")) {
3194                             savingComment = FALSE;
3195                         }
3196                     }
3197                     next_out = i;
3198                 } else if (started == STARTED_HOLDINGS) {
3199                     int gamenum;
3200                     char new_piece[MSG_SIZ];
3201                     started = STARTED_NONE;
3202                     parse[parse_pos] = NULLCHAR;
3203                     if (appData.debugMode)
3204                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3205                                                         parse, currentMove);
3206                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3207                         gamenum == ics_gamenum) {
3208                         if (gameInfo.variant == VariantNormal) {
3209                           /* [HGM] We seem to switch variant during a game!
3210                            * Presumably no holdings were displayed, so we have
3211                            * to move the position two files to the right to
3212                            * create room for them!
3213                            */
3214                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3215                           /* Get a move list just to see the header, which
3216                              will tell us whether this is really bug or zh */
3217                           if (ics_getting_history == H_FALSE) {
3218                             ics_getting_history = H_REQUESTED;
3219                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3220                             SendToICS(str);
3221                           }
3222                         }
3223                         new_piece[0] = NULLCHAR;
3224                         sscanf(parse, "game %d white [%s black [%s <- %s",
3225                                &gamenum, white_holding, black_holding,
3226                                new_piece);
3227                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3228                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3229                         /* [HGM] copy holdings to board holdings area */
3230                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3231                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3232 #if ZIPPY
3233                         if (appData.zippyPlay && first.initDone) {
3234                             ZippyHoldings(white_holding, black_holding,
3235                                           new_piece);
3236                         }
3237 #endif /*ZIPPY*/
3238                         if (tinyLayout || smallLayout) {
3239                             char wh[16], bh[16];
3240                             PackHolding(wh, white_holding);
3241                             PackHolding(bh, black_holding);
3242                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3243                                     gameInfo.white, gameInfo.black);
3244                         } else {
3245                             sprintf(str, "%s [%s] vs. %s [%s]",
3246                                     gameInfo.white, white_holding,
3247                                     gameInfo.black, black_holding);
3248                         }
3249
3250                         DrawPosition(FALSE, boards[currentMove]);
3251                         DisplayTitle(str);
3252                     }
3253                     /* Suppress following prompt */
3254                     if (looking_at(buf, &i, "*% ")) {
3255                         savingComment = FALSE;
3256                     }
3257                     next_out = i;
3258                 }
3259                 continue;
3260             }
3261
3262             i++;                /* skip unparsed character and loop back */
3263         }
3264         
3265         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3266             started != STARTED_HOLDINGS && i > next_out) {
3267             SendToPlayer(&buf[next_out], i - next_out);
3268             next_out = i;
3269         }
3270         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3271         
3272         leftover_len = buf_len - leftover_start;
3273         /* if buffer ends with something we couldn't parse,
3274            reparse it after appending the next read */
3275         
3276     } else if (count == 0) {
3277         RemoveInputSource(isr);
3278         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3279     } else {
3280         DisplayFatalError(_("Error reading from ICS"), error, 1);
3281     }
3282 }
3283
3284
3285 /* Board style 12 looks like this:
3286    
3287    <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
3288    
3289  * The "<12> " is stripped before it gets to this routine.  The two
3290  * trailing 0's (flip state and clock ticking) are later addition, and
3291  * some chess servers may not have them, or may have only the first.
3292  * Additional trailing fields may be added in the future.  
3293  */
3294
3295 #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"
3296
3297 #define RELATION_OBSERVING_PLAYED    0
3298 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3299 #define RELATION_PLAYING_MYMOVE      1
3300 #define RELATION_PLAYING_NOTMYMOVE  -1
3301 #define RELATION_EXAMINING           2
3302 #define RELATION_ISOLATED_BOARD     -3
3303 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3304
3305 void
3306 ParseBoard12(string)
3307      char *string;
3308
3309     GameMode newGameMode;
3310     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3311     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3312     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3313     char to_play, board_chars[200];
3314     char move_str[500], str[500], elapsed_time[500];
3315     char black[32], white[32];
3316     Board board;
3317     int prevMove = currentMove;
3318     int ticking = 2;
3319     ChessMove moveType;
3320     int fromX, fromY, toX, toY;
3321     char promoChar;
3322     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3323     char *bookHit = NULL; // [HGM] book
3324
3325     fromX = fromY = toX = toY = -1;
3326     
3327     newGame = FALSE;
3328
3329     if (appData.debugMode)
3330       fprintf(debugFP, _("Parsing board: %s\n"), string);
3331
3332     move_str[0] = NULLCHAR;
3333     elapsed_time[0] = NULLCHAR;
3334     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3335         int  i = 0, j;
3336         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3337             if(string[i] == ' ') { ranks++; files = 0; }
3338             else files++;
3339             i++;
3340         }
3341         for(j = 0; j <i; j++) board_chars[j] = string[j];
3342         board_chars[i] = '\0';
3343         string += i + 1;
3344     }
3345     n = sscanf(string, PATTERN, &to_play, &double_push,
3346                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3347                &gamenum, white, black, &relation, &basetime, &increment,
3348                &white_stren, &black_stren, &white_time, &black_time,
3349                &moveNum, str, elapsed_time, move_str, &ics_flip,
3350                &ticking);
3351
3352     if (n < 21) {
3353         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3354         DisplayError(str, 0);
3355         return;
3356     }
3357
3358     /* Convert the move number to internal form */
3359     moveNum = (moveNum - 1) * 2;
3360     if (to_play == 'B') moveNum++;
3361     if (moveNum >= MAX_MOVES) {
3362       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3363                         0, 1);
3364       return;
3365     }
3366     
3367     switch (relation) {
3368       case RELATION_OBSERVING_PLAYED:
3369       case RELATION_OBSERVING_STATIC:
3370         if (gamenum == -1) {
3371             /* Old ICC buglet */
3372             relation = RELATION_OBSERVING_STATIC;
3373         }
3374         newGameMode = IcsObserving;
3375         break;
3376       case RELATION_PLAYING_MYMOVE:
3377       case RELATION_PLAYING_NOTMYMOVE:
3378         newGameMode =
3379           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3380             IcsPlayingWhite : IcsPlayingBlack;
3381         break;
3382       case RELATION_EXAMINING:
3383         newGameMode = IcsExamining;
3384         break;
3385       case RELATION_ISOLATED_BOARD:
3386       default:
3387         /* Just display this board.  If user was doing something else,
3388            we will forget about it until the next board comes. */ 
3389         newGameMode = IcsIdle;
3390         break;
3391       case RELATION_STARTING_POSITION:
3392         newGameMode = gameMode;
3393         break;
3394     }
3395     
3396     /* Modify behavior for initial board display on move listing
3397        of wild games.
3398        */
3399     switch (ics_getting_history) {
3400       case H_FALSE:
3401       case H_REQUESTED:
3402         break;
3403       case H_GOT_REQ_HEADER:
3404       case H_GOT_UNREQ_HEADER:
3405         /* This is the initial position of the current game */
3406         gamenum = ics_gamenum;
3407         moveNum = 0;            /* old ICS bug workaround */
3408         if (to_play == 'B') {
3409           startedFromSetupPosition = TRUE;
3410           blackPlaysFirst = TRUE;
3411           moveNum = 1;
3412           if (forwardMostMove == 0) forwardMostMove = 1;
3413           if (backwardMostMove == 0) backwardMostMove = 1;
3414           if (currentMove == 0) currentMove = 1;
3415         }
3416         newGameMode = gameMode;
3417         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3418         break;
3419       case H_GOT_UNWANTED_HEADER:
3420         /* This is an initial board that we don't want */
3421         return;
3422       case H_GETTING_MOVES:
3423         /* Should not happen */
3424         DisplayError(_("Error gathering move list: extra board"), 0);
3425         ics_getting_history = H_FALSE;
3426         return;
3427     }
3428     
3429     /* Take action if this is the first board of a new game, or of a
3430        different game than is currently being displayed.  */
3431     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3432         relation == RELATION_ISOLATED_BOARD) {
3433         
3434         /* Forget the old game and get the history (if any) of the new one */
3435         if (gameMode != BeginningOfGame) {
3436           Reset(FALSE, TRUE);
3437         }
3438         newGame = TRUE;
3439         if (appData.autoRaiseBoard) BoardToTop();
3440         prevMove = -3;
3441         if (gamenum == -1) {
3442             newGameMode = IcsIdle;
3443         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3444                    appData.getMoveList) {
3445             /* Need to get game history */
3446             ics_getting_history = H_REQUESTED;
3447             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3448             SendToICS(str);
3449         }
3450         
3451         /* Initially flip the board to have black on the bottom if playing
3452            black or if the ICS flip flag is set, but let the user change
3453            it with the Flip View button. */
3454         flipView = appData.autoFlipView ? 
3455           (newGameMode == IcsPlayingBlack) || ics_flip :
3456           appData.flipView;
3457         
3458         /* Done with values from previous mode; copy in new ones */
3459         gameMode = newGameMode;
3460         ModeHighlight();
3461         ics_gamenum = gamenum;
3462         if (gamenum == gs_gamenum) {
3463             int klen = strlen(gs_kind);
3464             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3465             sprintf(str, "ICS %s", gs_kind);
3466             gameInfo.event = StrSave(str);
3467         } else {
3468             gameInfo.event = StrSave("ICS game");
3469         }
3470         gameInfo.site = StrSave(appData.icsHost);
3471         gameInfo.date = PGNDate();
3472         gameInfo.round = StrSave("-");
3473         gameInfo.white = StrSave(white);
3474         gameInfo.black = StrSave(black);
3475         timeControl = basetime * 60 * 1000;
3476         timeControl_2 = 0;
3477         timeIncrement = increment * 1000;
3478         movesPerSession = 0;
3479         gameInfo.timeControl = TimeControlTagValue();
3480         VariantSwitch(board, StringToVariant(gameInfo.event) );
3481   if (appData.debugMode) {
3482     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3483     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3484     setbuf(debugFP, NULL);
3485   }
3486
3487         gameInfo.outOfBook = NULL;
3488         
3489         /* Do we have the ratings? */
3490         if (strcmp(player1Name, white) == 0 &&
3491             strcmp(player2Name, black) == 0) {
3492             if (appData.debugMode)
3493               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3494                       player1Rating, player2Rating);
3495             gameInfo.whiteRating = player1Rating;
3496             gameInfo.blackRating = player2Rating;
3497         } else if (strcmp(player2Name, white) == 0 &&
3498                    strcmp(player1Name, black) == 0) {
3499             if (appData.debugMode)
3500               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3501                       player2Rating, player1Rating);
3502             gameInfo.whiteRating = player2Rating;
3503             gameInfo.blackRating = player1Rating;
3504         }
3505         player1Name[0] = player2Name[0] = NULLCHAR;
3506
3507         /* Silence shouts if requested */
3508         if (appData.quietPlay &&
3509             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3510             SendToICS(ics_prefix);
3511             SendToICS("set shout 0\n");
3512         }
3513     }
3514     
3515     /* Deal with midgame name changes */
3516     if (!newGame) {
3517         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3518             if (gameInfo.white) free(gameInfo.white);
3519             gameInfo.white = StrSave(white);
3520         }
3521         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3522             if (gameInfo.black) free(gameInfo.black);
3523             gameInfo.black = StrSave(black);
3524         }
3525     }
3526     
3527     /* Throw away game result if anything actually changes in examine mode */
3528     if (gameMode == IcsExamining && !newGame) {
3529         gameInfo.result = GameUnfinished;
3530         if (gameInfo.resultDetails != NULL) {
3531             free(gameInfo.resultDetails);
3532             gameInfo.resultDetails = NULL;
3533         }
3534     }
3535     
3536     /* In pausing && IcsExamining mode, we ignore boards coming
3537        in if they are in a different variation than we are. */
3538     if (pauseExamInvalid) return;
3539     if (pausing && gameMode == IcsExamining) {
3540         if (moveNum <= pauseExamForwardMostMove) {
3541             pauseExamInvalid = TRUE;
3542             forwardMostMove = pauseExamForwardMostMove;
3543             return;
3544         }
3545     }
3546     
3547   if (appData.debugMode) {
3548     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3549   }
3550     /* Parse the board */
3551     for (k = 0; k < ranks; k++) {
3552       for (j = 0; j < files; j++)
3553         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3554       if(gameInfo.holdingsWidth > 1) {
3555            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3556            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3557       }
3558     }
3559     CopyBoard(boards[moveNum], board);
3560     if (moveNum == 0) {
3561         startedFromSetupPosition =
3562           !CompareBoards(board, initialPosition);
3563         if(startedFromSetupPosition)
3564             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3565     }
3566
3567     /* [HGM] Set castling rights. Take the outermost Rooks,
3568        to make it also work for FRC opening positions. Note that board12
3569        is really defective for later FRC positions, as it has no way to
3570        indicate which Rook can castle if they are on the same side of King.
3571        For the initial position we grant rights to the outermost Rooks,
3572        and remember thos rights, and we then copy them on positions
3573        later in an FRC game. This means WB might not recognize castlings with
3574        Rooks that have moved back to their original position as illegal,
3575        but in ICS mode that is not its job anyway.
3576     */
3577     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3578     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3579
3580         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3581             if(board[0][i] == WhiteRook) j = i;
3582         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3583         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3584             if(board[0][i] == WhiteRook) j = i;
3585         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3586         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3587             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3588         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3589         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3590             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3591         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3592
3593         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3594         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3595             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3596         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3597             if(board[BOARD_HEIGHT-1][k] == bKing)
3598                 initialRights[5] = castlingRights[moveNum][5] = k;
3599     } else { int r;
3600         r = castlingRights[moveNum][0] = initialRights[0];
3601         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3602         r = castlingRights[moveNum][1] = initialRights[1];
3603         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3604         r = castlingRights[moveNum][3] = initialRights[3];
3605         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3606         r = castlingRights[moveNum][4] = initialRights[4];
3607         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3608         /* wildcastle kludge: always assume King has rights */
3609         r = castlingRights[moveNum][2] = initialRights[2];
3610         r = castlingRights[moveNum][5] = initialRights[5];
3611     }
3612     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3613     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3614
3615     
3616     if (ics_getting_history == H_GOT_REQ_HEADER ||
3617         ics_getting_history == H_GOT_UNREQ_HEADER) {
3618         /* This was an initial position from a move list, not
3619            the current position */
3620         return;
3621     }
3622     
3623     /* Update currentMove and known move number limits */
3624     newMove = newGame || moveNum > forwardMostMove;
3625
3626     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3627     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3628         takeback = forwardMostMove - moveNum;
3629         for (i = 0; i < takeback; i++) {
3630              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3631              SendToProgram("undo\n", &first);
3632         }
3633     }
3634
3635     if (newGame) {
3636         forwardMostMove = backwardMostMove = currentMove = moveNum;
3637         if (gameMode == IcsExamining && moveNum == 0) {
3638           /* Workaround for ICS limitation: we are not told the wild
3639              type when starting to examine a game.  But if we ask for
3640              the move list, the move list header will tell us */
3641             ics_getting_history = H_REQUESTED;
3642             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3643             SendToICS(str);
3644         }
3645     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3646                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3647         forwardMostMove = moveNum;
3648         if (!pausing || currentMove > forwardMostMove)
3649           currentMove = forwardMostMove;
3650     } else {
3651         /* New part of history that is not contiguous with old part */ 
3652         if (pausing && gameMode == IcsExamining) {
3653             pauseExamInvalid = TRUE;
3654             forwardMostMove = pauseExamForwardMostMove;
3655             return;
3656         }
3657         forwardMostMove = backwardMostMove = currentMove = moveNum;
3658         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3659             ics_getting_history = H_REQUESTED;
3660             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3661             SendToICS(str);
3662         }
3663     }
3664     
3665     /* Update the clocks */
3666     if (strchr(elapsed_time, '.')) {
3667       /* Time is in ms */
3668       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3669       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3670     } else {
3671       /* Time is in seconds */
3672       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3673       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3674     }
3675       
3676
3677 #if ZIPPY
3678     if (appData.zippyPlay && newGame &&
3679         gameMode != IcsObserving && gameMode != IcsIdle &&
3680         gameMode != IcsExamining)
3681       ZippyFirstBoard(moveNum, basetime, increment);
3682 #endif
3683     
3684     /* Put the move on the move list, first converting
3685        to canonical algebraic form. */
3686     if (moveNum > 0) {
3687   if (appData.debugMode) {
3688     if (appData.debugMode) { int f = forwardMostMove;
3689         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3690                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3691     }
3692     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3693     fprintf(debugFP, "moveNum = %d\n", moveNum);
3694     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3695     setbuf(debugFP, NULL);
3696   }
3697         if (moveNum <= backwardMostMove) {
3698             /* We don't know what the board looked like before
3699                this move.  Punt. */
3700             strcpy(parseList[moveNum - 1], move_str);
3701             strcat(parseList[moveNum - 1], " ");
3702             strcat(parseList[moveNum - 1], elapsed_time);
3703             moveList[moveNum - 1][0] = NULLCHAR;
3704         } else if (strcmp(move_str, "none") == 0) {
3705             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3706             /* Again, we don't know what the board looked like;
3707                this is really the start of the game. */
3708             parseList[moveNum - 1][0] = NULLCHAR;
3709             moveList[moveNum - 1][0] = NULLCHAR;
3710             backwardMostMove = moveNum;
3711             startedFromSetupPosition = TRUE;
3712             fromX = fromY = toX = toY = -1;
3713         } else {
3714           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3715           //                 So we parse the long-algebraic move string in stead of the SAN move
3716           int valid; char buf[MSG_SIZ], *prom;
3717
3718           // str looks something like "Q/a1-a2"; kill the slash
3719           if(str[1] == '/') 
3720                 sprintf(buf, "%c%s", str[0], str+2);
3721           else  strcpy(buf, str); // might be castling
3722           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3723                 strcat(buf, prom); // long move lacks promo specification!
3724           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3725                 if(appData.debugMode) 
3726                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3727                 strcpy(move_str, buf);
3728           }
3729           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3730                                 &fromX, &fromY, &toX, &toY, &promoChar)
3731                || ParseOneMove(buf, moveNum - 1, &moveType,
3732                                 &fromX, &fromY, &toX, &toY, &promoChar);
3733           // end of long SAN patch
3734           if (valid) {
3735             (void) CoordsToAlgebraic(boards[moveNum - 1],
3736                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3737                                      fromY, fromX, toY, toX, promoChar,
3738                                      parseList[moveNum-1]);
3739             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3740                              castlingRights[moveNum]) ) {
3741               case MT_NONE:
3742               case MT_STALEMATE:
3743               default:
3744                 break;
3745               case MT_CHECK:
3746                 if(gameInfo.variant != VariantShogi)
3747                     strcat(parseList[moveNum - 1], "+");
3748                 break;
3749               case MT_CHECKMATE:
3750               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3751                 strcat(parseList[moveNum - 1], "#");
3752                 break;
3753             }
3754             strcat(parseList[moveNum - 1], " ");
3755             strcat(parseList[moveNum - 1], elapsed_time);
3756             /* currentMoveString is set as a side-effect of ParseOneMove */
3757             strcpy(moveList[moveNum - 1], currentMoveString);
3758             strcat(moveList[moveNum - 1], "\n");
3759           } else {
3760             /* Move from ICS was illegal!?  Punt. */
3761   if (appData.debugMode) {
3762     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3763     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3764   }
3765 #if 0
3766             if (appData.testLegality && appData.debugMode) {
3767                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3768                 DisplayError(str, 0);
3769             }
3770 #endif
3771             strcpy(parseList[moveNum - 1], move_str);
3772             strcat(parseList[moveNum - 1], " ");
3773             strcat(parseList[moveNum - 1], elapsed_time);
3774             moveList[moveNum - 1][0] = NULLCHAR;
3775             fromX = fromY = toX = toY = -1;
3776           }
3777         }
3778   if (appData.debugMode) {
3779     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3780     setbuf(debugFP, NULL);
3781   }
3782
3783 #if ZIPPY
3784         /* Send move to chess program (BEFORE animating it). */
3785         if (appData.zippyPlay && !newGame && newMove && 
3786            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3787
3788             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3789                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3790                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3791                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3792                             move_str);
3793                     DisplayError(str, 0);
3794                 } else {
3795                     if (first.sendTime) {
3796                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3797                     }
3798                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3799                     if (firstMove && !bookHit) {
3800                         firstMove = FALSE;
3801                         if (first.useColors) {
3802                           SendToProgram(gameMode == IcsPlayingWhite ?
3803                                         "white\ngo\n" :
3804                                         "black\ngo\n", &first);
3805                         } else {
3806                           SendToProgram("go\n", &first);
3807                         }
3808                         first.maybeThinking = TRUE;
3809                     }
3810                 }
3811             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3812               if (moveList[moveNum - 1][0] == NULLCHAR) {
3813                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3814                 DisplayError(str, 0);
3815               } else {
3816                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3817                 SendMoveToProgram(moveNum - 1, &first);
3818               }
3819             }
3820         }
3821 #endif
3822     }
3823
3824     if (moveNum > 0 && !gotPremove) {
3825         /* If move comes from a remote source, animate it.  If it
3826            isn't remote, it will have already been animated. */
3827         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3828             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3829         }
3830         if (!pausing && appData.highlightLastMove) {
3831             SetHighlights(fromX, fromY, toX, toY);
3832         }
3833     }
3834     
3835     /* Start the clocks */
3836     whiteFlag = blackFlag = FALSE;
3837     appData.clockMode = !(basetime == 0 && increment == 0);
3838     if (ticking == 0) {
3839       ics_clock_paused = TRUE;
3840       StopClocks();
3841     } else if (ticking == 1) {
3842       ics_clock_paused = FALSE;
3843     }
3844     if (gameMode == IcsIdle ||
3845         relation == RELATION_OBSERVING_STATIC ||
3846         relation == RELATION_EXAMINING ||
3847         ics_clock_paused)
3848       DisplayBothClocks();
3849     else
3850       StartClocks();
3851     
3852     /* Display opponents and material strengths */
3853     if (gameInfo.variant != VariantBughouse &&
3854         gameInfo.variant != VariantCrazyhouse) {
3855         if (tinyLayout || smallLayout) {
3856             if(gameInfo.variant == VariantNormal)
3857                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3858                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3859                     basetime, increment);
3860             else
3861                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3862                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3863                     basetime, increment, (int) gameInfo.variant);
3864         } else {
3865             if(gameInfo.variant == VariantNormal)
3866                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3867                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3868                     basetime, increment);
3869             else
3870                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3871                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3872                     basetime, increment, VariantName(gameInfo.variant));
3873         }
3874         DisplayTitle(str);
3875   if (appData.debugMode) {
3876     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3877   }
3878     }
3879
3880    
3881     /* Display the board */
3882     if (!pausing) {
3883       
3884       if (appData.premove)
3885           if (!gotPremove || 
3886              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3887              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3888               ClearPremoveHighlights();
3889
3890       DrawPosition(FALSE, boards[currentMove]);
3891       DisplayMove(moveNum - 1);
3892       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3893             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3894               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3895         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3896       }
3897     }
3898
3899     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3900 #if ZIPPY
3901     if(bookHit) { // [HGM] book: simulate book reply
3902         static char bookMove[MSG_SIZ]; // a bit generous?
3903
3904         programStats.nodes = programStats.depth = programStats.time = 
3905         programStats.score = programStats.got_only_move = 0;
3906         sprintf(programStats.movelist, "%s (xbook)", bookHit);
3907
3908         strcpy(bookMove, "move ");
3909         strcat(bookMove, bookHit);
3910         HandleMachineMove(bookMove, &first);
3911     }
3912 #endif
3913 }
3914
3915 void
3916 GetMoveListEvent()
3917 {
3918     char buf[MSG_SIZ];
3919     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3920         ics_getting_history = H_REQUESTED;
3921         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3922         SendToICS(buf);
3923     }
3924 }
3925
3926 void
3927 AnalysisPeriodicEvent(force)
3928      int force;
3929 {
3930     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3931          && !force) || !appData.periodicUpdates)
3932       return;
3933
3934     /* Send . command to Crafty to collect stats */
3935     SendToProgram(".\n", &first);
3936
3937     /* Don't send another until we get a response (this makes
3938        us stop sending to old Crafty's which don't understand
3939        the "." command (sending illegal cmds resets node count & time,
3940        which looks bad)) */
3941     programStats.ok_to_send = 0;
3942 }
3943
3944 void
3945 SendMoveToProgram(moveNum, cps)
3946      int moveNum;
3947      ChessProgramState *cps;
3948 {
3949     char buf[MSG_SIZ];
3950
3951     if (cps->useUsermove) {
3952       SendToProgram("usermove ", cps);
3953     }
3954     if (cps->useSAN) {
3955       char *space;
3956       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3957         int len = space - parseList[moveNum];
3958         memcpy(buf, parseList[moveNum], len);
3959         buf[len++] = '\n';
3960         buf[len] = NULLCHAR;
3961       } else {
3962         sprintf(buf, "%s\n", parseList[moveNum]);
3963       }
3964       SendToProgram(buf, cps);
3965     } else {
3966       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3967         AlphaRank(moveList[moveNum], 4);
3968         SendToProgram(moveList[moveNum], cps);
3969         AlphaRank(moveList[moveNum], 4); // and back
3970       } else
3971       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3972        * the engine. It would be nice to have a better way to identify castle 
3973        * moves here. */
3974       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3975                                                                          && cps->useOOCastle) {
3976         int fromX = moveList[moveNum][0] - AAA; 
3977         int fromY = moveList[moveNum][1] - ONE;
3978         int toX = moveList[moveNum][2] - AAA; 
3979         int toY = moveList[moveNum][3] - ONE;
3980         if((boards[moveNum][fromY][fromX] == WhiteKing 
3981             && boards[moveNum][toY][toX] == WhiteRook)
3982            || (boards[moveNum][fromY][fromX] == BlackKing 
3983                && boards[moveNum][toY][toX] == BlackRook)) {
3984           if(toX > fromX) SendToProgram("O-O\n", cps);
3985           else SendToProgram("O-O-O\n", cps);
3986         }
3987         else SendToProgram(moveList[moveNum], cps);
3988       }
3989       else SendToProgram(moveList[moveNum], cps);
3990       /* End of additions by Tord */
3991     }
3992
3993     /* [HGM] setting up the opening has brought engine in force mode! */
3994     /*       Send 'go' if we are in a mode where machine should play. */
3995     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
3996         (gameMode == TwoMachinesPlay   ||
3997 #ifdef ZIPPY
3998          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
3999 #endif
4000          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4001         SendToProgram("go\n", cps);
4002   if (appData.debugMode) {
4003     fprintf(debugFP, "(extra)\n");
4004   }
4005     }
4006     setboardSpoiledMachineBlack = 0;
4007 }
4008
4009 void
4010 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4011      ChessMove moveType;
4012      int fromX, fromY, toX, toY;
4013 {
4014     char user_move[MSG_SIZ];
4015
4016     switch (moveType) {
4017       default:
4018         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4019                 (int)moveType, fromX, fromY, toX, toY);
4020         DisplayError(user_move + strlen("say "), 0);
4021         break;
4022       case WhiteKingSideCastle:
4023       case BlackKingSideCastle:
4024       case WhiteQueenSideCastleWild:
4025       case BlackQueenSideCastleWild:
4026       /* PUSH Fabien */
4027       case WhiteHSideCastleFR:
4028       case BlackHSideCastleFR:
4029       /* POP Fabien */
4030         sprintf(user_move, "o-o\n");
4031         break;
4032       case WhiteQueenSideCastle:
4033       case BlackQueenSideCastle:
4034       case WhiteKingSideCastleWild:
4035       case BlackKingSideCastleWild:
4036       /* PUSH Fabien */
4037       case WhiteASideCastleFR:
4038       case BlackASideCastleFR:
4039       /* POP Fabien */
4040         sprintf(user_move, "o-o-o\n");
4041         break;
4042       case WhitePromotionQueen:
4043       case BlackPromotionQueen:
4044       case WhitePromotionRook:
4045       case BlackPromotionRook:
4046       case WhitePromotionBishop:
4047       case BlackPromotionBishop:
4048       case WhitePromotionKnight:
4049       case BlackPromotionKnight:
4050       case WhitePromotionKing:
4051       case BlackPromotionKing:
4052       case WhitePromotionChancellor:
4053       case BlackPromotionChancellor:
4054       case WhitePromotionArchbishop:
4055       case BlackPromotionArchbishop:
4056         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4057             sprintf(user_move, "%c%c%c%c=%c\n",
4058                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4059                 PieceToChar(WhiteFerz));
4060         else if(gameInfo.variant == VariantGreat)
4061             sprintf(user_move, "%c%c%c%c=%c\n",
4062                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4063                 PieceToChar(WhiteMan));
4064         else
4065             sprintf(user_move, "%c%c%c%c=%c\n",
4066                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4067                 PieceToChar(PromoPiece(moveType)));
4068         break;
4069       case WhiteDrop:
4070       case BlackDrop:
4071         sprintf(user_move, "%c@%c%c\n",
4072                 ToUpper(PieceToChar((ChessSquare) fromX)),
4073                 AAA + toX, ONE + toY);
4074         break;
4075       case NormalMove:
4076       case WhiteCapturesEnPassant:
4077       case BlackCapturesEnPassant:
4078       case IllegalMove:  /* could be a variant we don't quite understand */
4079         sprintf(user_move, "%c%c%c%c\n",
4080                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4081         break;
4082     }
4083     SendToICS(user_move);
4084 }
4085
4086 void
4087 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4088      int rf, ff, rt, ft;
4089      char promoChar;
4090      char move[7];
4091 {
4092     if (rf == DROP_RANK) {
4093         sprintf(move, "%c@%c%c\n",
4094                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4095     } else {
4096         if (promoChar == 'x' || promoChar == NULLCHAR) {
4097             sprintf(move, "%c%c%c%c\n",
4098                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4099         } else {
4100             sprintf(move, "%c%c%c%c%c\n",
4101                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4102         }
4103     }
4104 }
4105
4106 void
4107 ProcessICSInitScript(f)
4108      FILE *f;
4109 {
4110     char buf[MSG_SIZ];
4111
4112     while (fgets(buf, MSG_SIZ, f)) {
4113         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4114     }
4115
4116     fclose(f);
4117 }
4118
4119
4120 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4121 void
4122 AlphaRank(char *move, int n)
4123 {
4124 //    char *p = move, c; int x, y;
4125
4126     if (appData.debugMode) {
4127         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4128     }
4129
4130     if(move[1]=='*' && 
4131        move[2]>='0' && move[2]<='9' &&
4132        move[3]>='a' && move[3]<='x'    ) {
4133         move[1] = '@';
4134         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4135         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4136     } else
4137     if(move[0]>='0' && move[0]<='9' &&
4138        move[1]>='a' && move[1]<='x' &&
4139        move[2]>='0' && move[2]<='9' &&
4140        move[3]>='a' && move[3]<='x'    ) {
4141         /* input move, Shogi -> normal */
4142         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4143         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4144         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4145         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4146     } else
4147     if(move[1]=='@' &&
4148        move[3]>='0' && move[3]<='9' &&
4149        move[2]>='a' && move[2]<='x'    ) {
4150         move[1] = '*';
4151         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4152         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4153     } else
4154     if(
4155        move[0]>='a' && move[0]<='x' &&
4156        move[3]>='0' && move[3]<='9' &&
4157        move[2]>='a' && move[2]<='x'    ) {
4158          /* output move, normal -> Shogi */
4159         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4160         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4161         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4162         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4163         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4164     }
4165     if (appData.debugMode) {
4166         fprintf(debugFP, "   out = '%s'\n", move);
4167     }
4168 }
4169
4170 /* Parser for moves from gnuchess, ICS, or user typein box */
4171 Boolean
4172 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4173      char *move;
4174      int moveNum;
4175      ChessMove *moveType;
4176      int *fromX, *fromY, *toX, *toY;
4177      char *promoChar;
4178 {       
4179     if (appData.debugMode) {
4180         fprintf(debugFP, "move to parse: %s\n", move);
4181     }
4182     *moveType = yylexstr(moveNum, move);
4183
4184     switch (*moveType) {
4185       case WhitePromotionChancellor:
4186       case BlackPromotionChancellor:
4187       case WhitePromotionArchbishop:
4188       case BlackPromotionArchbishop:
4189       case WhitePromotionQueen:
4190       case BlackPromotionQueen:
4191       case WhitePromotionRook:
4192       case BlackPromotionRook:
4193       case WhitePromotionBishop:
4194       case BlackPromotionBishop:
4195       case WhitePromotionKnight:
4196       case BlackPromotionKnight:
4197       case WhitePromotionKing:
4198       case BlackPromotionKing:
4199       case NormalMove:
4200       case WhiteCapturesEnPassant:
4201       case BlackCapturesEnPassant:
4202       case WhiteKingSideCastle:
4203       case WhiteQueenSideCastle:
4204       case BlackKingSideCastle:
4205       case BlackQueenSideCastle:
4206       case WhiteKingSideCastleWild:
4207       case WhiteQueenSideCastleWild:
4208       case BlackKingSideCastleWild:
4209       case BlackQueenSideCastleWild:
4210       /* Code added by Tord: */
4211       case WhiteHSideCastleFR:
4212       case WhiteASideCastleFR:
4213       case BlackHSideCastleFR:
4214       case BlackASideCastleFR:
4215       /* End of code added by Tord */
4216       case IllegalMove:         /* bug or odd chess variant */
4217         *fromX = currentMoveString[0] - AAA;
4218         *fromY = currentMoveString[1] - ONE;
4219         *toX = currentMoveString[2] - AAA;
4220         *toY = currentMoveString[3] - ONE;
4221         *promoChar = currentMoveString[4];
4222         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4223             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4224     if (appData.debugMode) {
4225         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4226     }
4227             *fromX = *fromY = *toX = *toY = 0;
4228             return FALSE;
4229         }
4230         if (appData.testLegality) {
4231           return (*moveType != IllegalMove);
4232         } else {
4233           return !(fromX == fromY && toX == toY);
4234         }
4235
4236       case WhiteDrop:
4237       case BlackDrop:
4238         *fromX = *moveType == WhiteDrop ?
4239           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4240           (int) CharToPiece(ToLower(currentMoveString[0]));
4241         *fromY = DROP_RANK;
4242         *toX = currentMoveString[2] - AAA;
4243         *toY = currentMoveString[3] - ONE;
4244         *promoChar = NULLCHAR;
4245         return TRUE;
4246
4247       case AmbiguousMove:
4248       case ImpossibleMove:
4249       case (ChessMove) 0:       /* end of file */
4250       case ElapsedTime:
4251       case Comment:
4252       case PGNTag:
4253       case NAG:
4254       case WhiteWins:
4255       case BlackWins:
4256       case GameIsDrawn:
4257       default:
4258     if (appData.debugMode) {
4259         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4260     }
4261         /* bug? */
4262         *fromX = *fromY = *toX = *toY = 0;
4263         *promoChar = NULLCHAR;
4264         return FALSE;
4265     }
4266 }
4267
4268 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4269 // All positions will have equal probability, but the current method will not provide a unique
4270 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4271 #define DARK 1
4272 #define LITE 2
4273 #define ANY 3
4274
4275 int squaresLeft[4];
4276 int piecesLeft[(int)BlackPawn];
4277 int seed, nrOfShuffles;
4278
4279 void GetPositionNumber()
4280 {       // sets global variable seed
4281         int i;
4282
4283         seed = appData.defaultFrcPosition;
4284         if(seed < 0) { // randomize based on time for negative FRC position numbers
4285                 for(i=0; i<50; i++) seed += random();
4286                 seed = random() ^ random() >> 8 ^ random() << 8;
4287                 if(seed<0) seed = -seed;
4288         }
4289 }
4290
4291 int put(Board board, int pieceType, int rank, int n, int shade)
4292 // put the piece on the (n-1)-th empty squares of the given shade
4293 {
4294         int i;
4295
4296         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4297                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4298                         board[rank][i] = (ChessSquare) pieceType;
4299                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4300                         squaresLeft[ANY]--;
4301                         piecesLeft[pieceType]--; 
4302                         return i;
4303                 }
4304         }
4305         return -1;
4306 }
4307
4308
4309 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4310 // calculate where the next piece goes, (any empty square), and put it there
4311 {
4312         int i;
4313
4314         i = seed % squaresLeft[shade];
4315         nrOfShuffles *= squaresLeft[shade];
4316         seed /= squaresLeft[shade];
4317         put(board, pieceType, rank, i, shade);
4318 }
4319
4320 void AddTwoPieces(Board board, int pieceType, int rank)
4321 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4322 {
4323         int i, n=squaresLeft[ANY], j=n-1, k;
4324
4325         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4326         i = seed % k;  // pick one
4327         nrOfShuffles *= k;
4328         seed /= k;
4329         while(i >= j) i -= j--;
4330         j = n - 1 - j; i += j;
4331         put(board, pieceType, rank, j, ANY);
4332         put(board, pieceType, rank, i, ANY);
4333 }
4334
4335 void SetUpShuffle(Board board, int number)
4336 {
4337         int i, p, first=1;
4338
4339         GetPositionNumber(); nrOfShuffles = 1;
4340
4341         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4342         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4343         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4344
4345         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4346
4347         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4348             p = (int) board[0][i];
4349             if(p < (int) BlackPawn) piecesLeft[p] ++;
4350             board[0][i] = EmptySquare;
4351         }
4352
4353         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4354             // shuffles restricted to allow normal castling put KRR first
4355             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4356                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4357             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4358                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4359             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4360                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4361             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4362                 put(board, WhiteRook, 0, 0, ANY);
4363             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4364         }
4365
4366         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4367             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4368             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4369                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4370                 while(piecesLeft[p] >= 2) {
4371                     AddOnePiece(board, p, 0, LITE);
4372                     AddOnePiece(board, p, 0, DARK);
4373                 }
4374                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4375             }
4376
4377         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4378             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4379             // but we leave King and Rooks for last, to possibly obey FRC restriction
4380             if(p == (int)WhiteRook) continue;
4381             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4382             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4383         }
4384
4385         // now everything is placed, except perhaps King (Unicorn) and Rooks
4386
4387         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4388             // Last King gets castling rights
4389             while(piecesLeft[(int)WhiteUnicorn]) {
4390                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4391                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4392             }
4393
4394             while(piecesLeft[(int)WhiteKing]) {
4395                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4396                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4397             }
4398
4399
4400         } else {
4401             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4402             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4403         }
4404
4405         // Only Rooks can be left; simply place them all
4406         while(piecesLeft[(int)WhiteRook]) {
4407                 i = put(board, WhiteRook, 0, 0, ANY);
4408                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4409                         if(first) {
4410                                 first=0;
4411                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4412                         }
4413                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4414                 }
4415         }
4416         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4417             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4418         }
4419
4420         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4421 }
4422
4423 int SetCharTable( char *table, const char * map )
4424 /* [HGM] moved here from winboard.c because of its general usefulness */
4425 /*       Basically a safe strcpy that uses the last character as King */
4426 {
4427     int result = FALSE; int NrPieces;
4428
4429     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4430                     && NrPieces >= 12 && !(NrPieces&1)) {
4431         int i; /* [HGM] Accept even length from 12 to 34 */
4432
4433         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4434         for( i=0; i<NrPieces/2-1; i++ ) {
4435             table[i] = map[i];
4436             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4437         }
4438         table[(int) WhiteKing]  = map[NrPieces/2-1];
4439         table[(int) BlackKing]  = map[NrPieces-1];
4440
4441         result = TRUE;
4442     }
4443
4444     return result;
4445 }
4446
4447 void Prelude(Board board)
4448 {       // [HGM] superchess: random selection of exo-pieces
4449         int i, j, k; ChessSquare p; 
4450         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4451
4452         GetPositionNumber(); // use FRC position number
4453
4454         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4455             SetCharTable(pieceToChar, appData.pieceToCharTable);
4456             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4457                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4458         }
4459
4460         j = seed%4;                 seed /= 4; 
4461         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4462         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4463         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4464         j = seed%3 + (seed%3 >= j); seed /= 3; 
4465         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4466         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4467         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4468         j = seed%3;                 seed /= 3; 
4469         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4470         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4471         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4472         j = seed%2 + (seed%2 >= j); seed /= 2; 
4473         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4474         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4475         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4476         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4477         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4478         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4479         put(board, exoPieces[0],    0, 0, ANY);
4480         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4481 }
4482
4483 void
4484 InitPosition(redraw)
4485      int redraw;
4486 {
4487     ChessSquare (* pieces)[BOARD_SIZE];
4488     int i, j, pawnRow, overrule,
4489     oldx = gameInfo.boardWidth,
4490     oldy = gameInfo.boardHeight,
4491     oldh = gameInfo.holdingsWidth,
4492     oldv = gameInfo.variant;
4493
4494     currentMove = forwardMostMove = backwardMostMove = 0;
4495     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4496
4497     /* [AS] Initialize pv info list [HGM] and game status */
4498     {
4499         for( i=0; i<MAX_MOVES; i++ ) {
4500             pvInfoList[i].depth = 0;
4501             epStatus[i]=EP_NONE;
4502             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4503         }
4504
4505         initialRulePlies = 0; /* 50-move counter start */
4506
4507         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4508         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4509     }
4510
4511     
4512     /* [HGM] logic here is completely changed. In stead of full positions */
4513     /* the initialized data only consist of the two backranks. The switch */
4514     /* selects which one we will use, which is than copied to the Board   */
4515     /* initialPosition, which for the rest is initialized by Pawns and    */
4516     /* empty squares. This initial position is then copied to boards[0],  */
4517     /* possibly after shuffling, so that it remains available.            */
4518
4519     gameInfo.holdingsWidth = 0; /* default board sizes */
4520     gameInfo.boardWidth    = 8;
4521     gameInfo.boardHeight   = 8;
4522     gameInfo.holdingsSize  = 0;
4523     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4524     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4525     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4526
4527     switch (gameInfo.variant) {
4528     case VariantFischeRandom:
4529       shuffleOpenings = TRUE;
4530     default:
4531       pieces = FIDEArray;
4532       break;
4533     case VariantShatranj:
4534       pieces = ShatranjArray;
4535       nrCastlingRights = 0;
4536       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4537       break;
4538     case VariantTwoKings:
4539       pieces = twoKingsArray;
4540       break;
4541     case VariantCapaRandom:
4542       shuffleOpenings = TRUE;
4543     case VariantCapablanca:
4544       pieces = CapablancaArray;
4545       gameInfo.boardWidth = 10;
4546       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4547       break;
4548     case VariantGothic:
4549       pieces = GothicArray;
4550       gameInfo.boardWidth = 10;
4551       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4552       break;
4553     case VariantJanus:
4554       pieces = JanusArray;
4555       gameInfo.boardWidth = 10;
4556       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4557       nrCastlingRights = 6;
4558         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4559         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4560         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4561         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4562         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4563         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4564       break;
4565     case VariantFalcon:
4566       pieces = FalconArray;
4567       gameInfo.boardWidth = 10;
4568       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4569       break;
4570     case VariantXiangqi:
4571       pieces = XiangqiArray;
4572       gameInfo.boardWidth  = 9;
4573       gameInfo.boardHeight = 10;
4574       nrCastlingRights = 0;
4575       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4576       break;
4577     case VariantShogi:
4578       pieces = ShogiArray;
4579       gameInfo.boardWidth  = 9;
4580       gameInfo.boardHeight = 9;
4581       gameInfo.holdingsSize = 7;
4582       nrCastlingRights = 0;
4583       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4584       break;
4585     case VariantCourier:
4586       pieces = CourierArray;
4587       gameInfo.boardWidth  = 12;
4588       nrCastlingRights = 0;
4589       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4590       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4591       break;
4592     case VariantKnightmate:
4593       pieces = KnightmateArray;
4594       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4595       break;
4596     case VariantFairy:
4597       pieces = fairyArray;
4598       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4599       break;
4600     case VariantGreat:
4601       pieces = GreatArray;
4602       gameInfo.boardWidth = 10;
4603       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4604       gameInfo.holdingsSize = 8;
4605       break;
4606     case VariantSuper:
4607       pieces = FIDEArray;
4608       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4609       gameInfo.holdingsSize = 8;
4610       startedFromSetupPosition = TRUE;
4611       break;
4612     case VariantCrazyhouse:
4613     case VariantBughouse:
4614       pieces = FIDEArray;
4615       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4616       gameInfo.holdingsSize = 5;
4617       break;
4618     case VariantWildCastle:
4619       pieces = FIDEArray;
4620       /* !!?shuffle with kings guaranteed to be on d or e file */
4621       shuffleOpenings = 1;
4622       break;
4623     case VariantNoCastle:
4624       pieces = FIDEArray;
4625       nrCastlingRights = 0;
4626       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4627       /* !!?unconstrained back-rank shuffle */
4628       shuffleOpenings = 1;
4629       break;
4630     }
4631
4632     overrule = 0;
4633     if(appData.NrFiles >= 0) {
4634         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4635         gameInfo.boardWidth = appData.NrFiles;
4636     }
4637     if(appData.NrRanks >= 0) {
4638         gameInfo.boardHeight = appData.NrRanks;
4639     }
4640     if(appData.holdingsSize >= 0) {
4641         i = appData.holdingsSize;
4642         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4643         gameInfo.holdingsSize = i;
4644     }
4645     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4646     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4647         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4648
4649     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4650     if(pawnRow < 1) pawnRow = 1;
4651
4652     /* User pieceToChar list overrules defaults */
4653     if(appData.pieceToCharTable != NULL)
4654         SetCharTable(pieceToChar, appData.pieceToCharTable);
4655
4656     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4657
4658         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4659             s = (ChessSquare) 0; /* account holding counts in guard band */
4660         for( i=0; i<BOARD_HEIGHT; i++ )
4661             initialPosition[i][j] = s;
4662
4663         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4664         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4665         initialPosition[pawnRow][j] = WhitePawn;
4666         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4667         if(gameInfo.variant == VariantXiangqi) {
4668             if(j&1) {
4669                 initialPosition[pawnRow][j] = 
4670                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4671                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4672                    initialPosition[2][j] = WhiteCannon;
4673                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4674                 }
4675             }
4676         }
4677         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4678     }
4679     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4680
4681             j=BOARD_LEFT+1;
4682             initialPosition[1][j] = WhiteBishop;
4683             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4684             j=BOARD_RGHT-2;
4685             initialPosition[1][j] = WhiteRook;
4686             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4687     }
4688
4689     if( nrCastlingRights == -1) {
4690         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4691         /*       This sets default castling rights from none to normal corners   */
4692         /* Variants with other castling rights must set them themselves above    */
4693         nrCastlingRights = 6;
4694        
4695         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4696         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4697         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4698         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4699         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4700         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4701      }
4702
4703      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4704      if(gameInfo.variant == VariantGreat) { // promotion commoners
4705         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4706         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4707         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4708         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4709      }
4710 #if 0
4711     if(gameInfo.variant == VariantFischeRandom) {
4712       if( appData.defaultFrcPosition < 0 ) {
4713         ShuffleFRC( initialPosition );
4714       }
4715       else {
4716         SetupFRC( initialPosition, appData.defaultFrcPosition );
4717       }
4718       startedFromSetupPosition = TRUE;
4719     } else 
4720 #else
4721   if (appData.debugMode) {
4722     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4723   }
4724     if(shuffleOpenings) {
4725         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4726         startedFromSetupPosition = TRUE;
4727     }
4728 #endif
4729     if(startedFromPositionFile) {
4730       /* [HGM] loadPos: use PositionFile for every new game */
4731       CopyBoard(initialPosition, filePosition);
4732       for(i=0; i<nrCastlingRights; i++)
4733           castlingRights[0][i] = initialRights[i] = fileRights[i];
4734       startedFromSetupPosition = TRUE;
4735     }
4736
4737     CopyBoard(boards[0], initialPosition);
4738
4739     if(oldx != gameInfo.boardWidth ||
4740        oldy != gameInfo.boardHeight ||
4741        oldh != gameInfo.holdingsWidth
4742 #ifdef GOTHIC
4743        || oldv == VariantGothic ||        // For licensing popups
4744        gameInfo.variant == VariantGothic
4745 #endif
4746 #ifdef FALCON
4747        || oldv == VariantFalcon ||
4748        gameInfo.variant == VariantFalcon
4749 #endif
4750                                          )
4751             InitDrawingSizes(-2 ,0);
4752
4753     if (redraw)
4754       DrawPosition(TRUE, boards[currentMove]);
4755 }
4756
4757 void
4758 SendBoard(cps, moveNum)
4759      ChessProgramState *cps;
4760      int moveNum;
4761 {
4762     char message[MSG_SIZ];
4763     
4764     if (cps->useSetboard) {
4765       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4766       sprintf(message, "setboard %s\n", fen);
4767       SendToProgram(message, cps);
4768       free(fen);
4769
4770     } else {
4771       ChessSquare *bp;
4772       int i, j;
4773       /* Kludge to set black to move, avoiding the troublesome and now
4774        * deprecated "black" command.
4775        */
4776       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4777
4778       SendToProgram("edit\n", cps);
4779       SendToProgram("#\n", cps);
4780       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4781         bp = &boards[moveNum][i][BOARD_LEFT];
4782         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4783           if ((int) *bp < (int) BlackPawn) {
4784             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4785                     AAA + j, ONE + i);
4786             if(message[0] == '+' || message[0] == '~') {
4787                 sprintf(message, "%c%c%c+\n",
4788                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4789                         AAA + j, ONE + i);
4790             }
4791             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4792                 message[1] = BOARD_RGHT   - 1 - j + '1';
4793                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4794             }
4795             SendToProgram(message, cps);
4796           }
4797         }
4798       }
4799     
4800       SendToProgram("c\n", cps);
4801       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4802         bp = &boards[moveNum][i][BOARD_LEFT];
4803         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4804           if (((int) *bp != (int) EmptySquare)
4805               && ((int) *bp >= (int) BlackPawn)) {
4806             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4807                     AAA + j, ONE + i);
4808             if(message[0] == '+' || message[0] == '~') {
4809                 sprintf(message, "%c%c%c+\n",
4810                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4811                         AAA + j, ONE + i);
4812             }
4813             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4814                 message[1] = BOARD_RGHT   - 1 - j + '1';
4815                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4816             }
4817             SendToProgram(message, cps);
4818           }
4819         }
4820       }
4821     
4822       SendToProgram(".\n", cps);
4823     }
4824     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4825 }
4826
4827 int
4828 IsPromotion(fromX, fromY, toX, toY)
4829      int fromX, fromY, toX, toY;
4830 {
4831     /* [HGM] add Shogi promotions */
4832     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4833     ChessSquare piece;
4834
4835     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4836       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4837    /* [HGM] Note to self: line above also weeds out drops */
4838     piece = boards[currentMove][fromY][fromX];
4839     if(gameInfo.variant == VariantShogi) {
4840         promotionZoneSize = 3;
4841         highestPromotingPiece = (int)WhiteKing;
4842         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4843            and if in normal chess we then allow promotion to King, why not
4844            allow promotion of other piece in Shogi?                         */
4845     }
4846     if((int)piece >= BlackPawn) {
4847         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4848              return FALSE;
4849         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4850     } else {
4851         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4852            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4853     }
4854     return ( (int)piece <= highestPromotingPiece );
4855 }
4856
4857 int
4858 InPalace(row, column)
4859      int row, column;
4860 {   /* [HGM] for Xiangqi */
4861     if( (row < 3 || row > BOARD_HEIGHT-4) &&
4862          column < (BOARD_WIDTH + 4)/2 &&
4863          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4864     return FALSE;
4865 }
4866
4867 int
4868 PieceForSquare (x, y)
4869      int x;
4870      int y;
4871 {
4872   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4873      return -1;
4874   else
4875      return boards[currentMove][y][x];
4876 }
4877
4878 int
4879 OKToStartUserMove(x, y)
4880      int x, y;
4881 {
4882     ChessSquare from_piece;
4883     int white_piece;
4884
4885     if (matchMode) return FALSE;
4886     if (gameMode == EditPosition) return TRUE;
4887
4888     if (x >= 0 && y >= 0)
4889       from_piece = boards[currentMove][y][x];
4890     else
4891       from_piece = EmptySquare;
4892
4893     if (from_piece == EmptySquare) return FALSE;
4894
4895     white_piece = (int)from_piece >= (int)WhitePawn &&
4896       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4897
4898     switch (gameMode) {
4899       case PlayFromGameFile:
4900       case AnalyzeFile:
4901       case TwoMachinesPlay:
4902       case EndOfGame:
4903         return FALSE;
4904
4905       case IcsObserving:
4906       case IcsIdle:
4907         return FALSE;
4908
4909       case MachinePlaysWhite:
4910       case IcsPlayingBlack:
4911         if (appData.zippyPlay) return FALSE;
4912         if (white_piece) {
4913             DisplayMoveError(_("You are playing Black"));
4914             return FALSE;
4915         }
4916         break;
4917
4918       case MachinePlaysBlack:
4919       case IcsPlayingWhite:
4920         if (appData.zippyPlay) return FALSE;
4921         if (!white_piece) {
4922             DisplayMoveError(_("You are playing White"));
4923             return FALSE;
4924         }
4925         break;
4926
4927       case EditGame:
4928         if (!white_piece && WhiteOnMove(currentMove)) {
4929             DisplayMoveError(_("It is White's turn"));
4930             return FALSE;
4931         }           
4932         if (white_piece && !WhiteOnMove(currentMove)) {
4933             DisplayMoveError(_("It is Black's turn"));
4934             return FALSE;
4935         }           
4936         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4937             /* Editing correspondence game history */
4938             /* Could disallow this or prompt for confirmation */
4939             cmailOldMove = -1;
4940         }
4941         if (currentMove < forwardMostMove) {
4942             /* Discarding moves */
4943             /* Could prompt for confirmation here,
4944                but I don't think that's such a good idea */
4945             forwardMostMove = currentMove;
4946         }
4947         break;
4948
4949       case BeginningOfGame:
4950         if (appData.icsActive) return FALSE;
4951         if (!appData.noChessProgram) {
4952             if (!white_piece) {
4953                 DisplayMoveError(_("You are playing White"));
4954                 return FALSE;
4955             }
4956         }
4957         break;
4958         
4959       case Training:
4960         if (!white_piece && WhiteOnMove(currentMove)) {
4961             DisplayMoveError(_("It is White's turn"));
4962             return FALSE;
4963         }           
4964         if (white_piece && !WhiteOnMove(currentMove)) {
4965             DisplayMoveError(_("It is Black's turn"));
4966             return FALSE;
4967         }           
4968         break;
4969
4970       default:
4971       case IcsExamining:
4972         break;
4973     }
4974     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
4975         && gameMode != AnalyzeFile && gameMode != Training) {
4976         DisplayMoveError(_("Displayed position is not current"));
4977         return FALSE;
4978     }
4979     return TRUE;
4980 }
4981
4982 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
4983 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
4984 int lastLoadGameUseList = FALSE;
4985 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
4986 ChessMove lastLoadGameStart = (ChessMove) 0;
4987
4988
4989 ChessMove
4990 UserMoveTest(fromX, fromY, toX, toY, promoChar)
4991      int fromX, fromY, toX, toY;
4992      int promoChar;
4993 {
4994     ChessMove moveType;
4995     ChessSquare pdown, pup;
4996
4997     if (fromX < 0 || fromY < 0) return ImpossibleMove;
4998     if ((fromX == toX) && (fromY == toY)) {
4999         return ImpossibleMove;
5000     }
5001
5002     /* [HGM] suppress all moves into holdings area and guard band */
5003     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5004             return ImpossibleMove;
5005
5006     /* [HGM] <sameColor> moved to here from winboard.c */
5007     /* note: this code seems to exist for filtering out some obviously illegal premoves */
5008     pdown = boards[currentMove][fromY][fromX];
5009     pup = boards[currentMove][toY][toX];
5010     if (    gameMode != EditPosition &&
5011             (WhitePawn <= pdown && pdown < BlackPawn &&
5012              WhitePawn <= pup && pup < BlackPawn  ||
5013              BlackPawn <= pdown && pdown < EmptySquare &&
5014              BlackPawn <= pup && pup < EmptySquare 
5015             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5016                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5017                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) 
5018         )           )
5019          return ImpossibleMove;
5020
5021     /* Check if the user is playing in turn.  This is complicated because we
5022        let the user "pick up" a piece before it is his turn.  So the piece he
5023        tried to pick up may have been captured by the time he puts it down!
5024        Therefore we use the color the user is supposed to be playing in this
5025        test, not the color of the piece that is currently on the starting
5026        square---except in EditGame mode, where the user is playing both
5027        sides; fortunately there the capture race can't happen.  (It can
5028        now happen in IcsExamining mode, but that's just too bad.  The user
5029        will get a somewhat confusing message in that case.)
5030        */
5031
5032     switch (gameMode) {
5033       case PlayFromGameFile:
5034       case AnalyzeFile:
5035       case TwoMachinesPlay:
5036       case EndOfGame:
5037       case IcsObserving:
5038       case IcsIdle:
5039         /* We switched into a game mode where moves are not accepted,
5040            perhaps while the mouse button was down. */
5041         return ImpossibleMove;
5042
5043       case MachinePlaysWhite:
5044         /* User is moving for Black */
5045         if (WhiteOnMove(currentMove)) {
5046             DisplayMoveError(_("It is White's turn"));
5047             return ImpossibleMove;
5048         }
5049         break;
5050
5051       case MachinePlaysBlack:
5052         /* User is moving for White */
5053         if (!WhiteOnMove(currentMove)) {
5054             DisplayMoveError(_("It is Black's turn"));
5055             return ImpossibleMove;
5056         }
5057         break;
5058
5059       case EditGame:
5060       case IcsExamining:
5061       case BeginningOfGame:
5062       case AnalyzeMode:
5063       case Training:
5064         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5065             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5066             /* User is moving for Black */
5067             if (WhiteOnMove(currentMove)) {
5068                 DisplayMoveError(_("It is White's turn"));
5069                 return ImpossibleMove;
5070             }
5071         } else {
5072             /* User is moving for White */
5073             if (!WhiteOnMove(currentMove)) {
5074                 DisplayMoveError(_("It is Black's turn"));
5075                 return ImpossibleMove;
5076             }
5077         }
5078         break;
5079
5080       case IcsPlayingBlack:
5081         /* User is moving for Black */
5082         if (WhiteOnMove(currentMove)) {
5083             if (!appData.premove) {
5084                 DisplayMoveError(_("It is White's turn"));
5085             } else if (toX >= 0 && toY >= 0) {
5086                 premoveToX = toX;
5087                 premoveToY = toY;
5088                 premoveFromX = fromX;
5089                 premoveFromY = fromY;
5090                 premovePromoChar = promoChar;
5091                 gotPremove = 1;
5092                 if (appData.debugMode) 
5093                     fprintf(debugFP, "Got premove: fromX %d,"
5094                             "fromY %d, toX %d, toY %d\n",
5095                             fromX, fromY, toX, toY);
5096             }
5097             return ImpossibleMove;
5098         }
5099         break;
5100
5101       case IcsPlayingWhite:
5102         /* User is moving for White */
5103         if (!WhiteOnMove(currentMove)) {
5104             if (!appData.premove) {
5105                 DisplayMoveError(_("It is Black's turn"));
5106             } else if (toX >= 0 && toY >= 0) {
5107                 premoveToX = toX;
5108                 premoveToY = toY;
5109                 premoveFromX = fromX;
5110                 premoveFromY = fromY;
5111                 premovePromoChar = promoChar;
5112                 gotPremove = 1;
5113                 if (appData.debugMode) 
5114                     fprintf(debugFP, "Got premove: fromX %d,"
5115                             "fromY %d, toX %d, toY %d\n",
5116                             fromX, fromY, toX, toY);
5117             }
5118             return ImpossibleMove;
5119         }
5120         break;
5121
5122       default:
5123         break;
5124
5125       case EditPosition:
5126         /* EditPosition, empty square, or different color piece;
5127            click-click move is possible */
5128         if (toX == -2 || toY == -2) {
5129             boards[0][fromY][fromX] = EmptySquare;
5130             return AmbiguousMove;
5131         } else if (toX >= 0 && toY >= 0) {
5132             boards[0][toY][toX] = boards[0][fromY][fromX];
5133             boards[0][fromY][fromX] = EmptySquare;
5134             return AmbiguousMove;
5135         }
5136         return ImpossibleMove;
5137     }
5138
5139     /* [HGM] If move started in holdings, it means a drop */
5140     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5141          if( pup != EmptySquare ) return ImpossibleMove;
5142          if(appData.testLegality) {
5143              /* it would be more logical if LegalityTest() also figured out
5144               * which drops are legal. For now we forbid pawns on back rank.
5145               * Shogi is on its own here...
5146               */
5147              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5148                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5149                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5150          }
5151          return WhiteDrop; /* Not needed to specify white or black yet */
5152     }
5153
5154     userOfferedDraw = FALSE;
5155         
5156     /* [HGM] always test for legality, to get promotion info */
5157     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5158                           epStatus[currentMove], castlingRights[currentMove],
5159                                          fromY, fromX, toY, toX, promoChar);
5160
5161     /* [HGM] but possibly ignore an IllegalMove result */
5162     if (appData.testLegality) {
5163         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5164             DisplayMoveError(_("Illegal move"));
5165             return ImpossibleMove;
5166         }
5167     }
5168 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5169     return moveType;
5170     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5171        function is made into one that returns an OK move type if FinishMove
5172        should be called. This to give the calling driver routine the
5173        opportunity to finish the userMove input with a promotion popup,
5174        without bothering the user with this for invalid or illegal moves */
5175
5176 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5177 }
5178
5179 /* Common tail of UserMoveEvent and DropMenuEvent */
5180 int
5181 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5182      ChessMove moveType;
5183      int fromX, fromY, toX, toY;
5184      /*char*/int promoChar;
5185 {
5186     char *bookHit = 0;
5187 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5188     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5189         // [HGM] superchess: suppress promotions to non-available piece
5190         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5191         if(WhiteOnMove(currentMove)) {
5192             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5193         } else {
5194             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5195         }
5196     }
5197
5198     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5199        move type in caller when we know the move is a legal promotion */
5200     if(moveType == NormalMove && promoChar)
5201         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5202 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5203     /* [HGM] convert drag-and-drop piece drops to standard form */
5204     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5205          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5206          fromX = boards[currentMove][fromY][fromX];
5207          fromY = DROP_RANK;
5208     }
5209
5210     /* [HGM] <popupFix> The following if has been moved here from
5211        UserMoveEvent(). Because it seemed to belon here (why not allow
5212        piece drops in training games?), and because it can only be
5213        performed after it is known to what we promote. */
5214     if (gameMode == Training) {
5215       /* compare the move played on the board to the next move in the
5216        * game. If they match, display the move and the opponent's response. 
5217        * If they don't match, display an error message.
5218        */
5219       int saveAnimate;
5220       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5221       CopyBoard(testBoard, boards[currentMove]);
5222       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5223
5224       if (CompareBoards(testBoard, boards[currentMove+1])) {
5225         ForwardInner(currentMove+1);
5226
5227         /* Autoplay the opponent's response.
5228          * if appData.animate was TRUE when Training mode was entered,
5229          * the response will be animated.
5230          */
5231         saveAnimate = appData.animate;
5232         appData.animate = animateTraining;
5233         ForwardInner(currentMove+1);
5234         appData.animate = saveAnimate;
5235
5236         /* check for the end of the game */
5237         if (currentMove >= forwardMostMove) {
5238           gameMode = PlayFromGameFile;
5239           ModeHighlight();
5240           SetTrainingModeOff();
5241           DisplayInformation(_("End of game"));
5242         }
5243       } else {
5244         DisplayError(_("Incorrect move"), 0);
5245       }
5246       return 1;
5247     }
5248
5249   /* Ok, now we know that the move is good, so we can kill
5250      the previous line in Analysis Mode */
5251   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5252     forwardMostMove = currentMove;
5253   }
5254
5255   /* If we need the chess program but it's dead, restart it */
5256   ResurrectChessProgram();
5257
5258   /* A user move restarts a paused game*/
5259   if (pausing)
5260     PauseEvent();
5261
5262   thinkOutput[0] = NULLCHAR;
5263
5264   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5265
5266   if (gameMode == BeginningOfGame) {
5267     if (appData.noChessProgram) {
5268       gameMode = EditGame;
5269       SetGameInfo();
5270     } else {
5271       char buf[MSG_SIZ];
5272       gameMode = MachinePlaysBlack;
5273       StartClocks();
5274       SetGameInfo();
5275       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5276       DisplayTitle(buf);
5277       if (first.sendName) {
5278         sprintf(buf, "name %s\n", gameInfo.white);
5279         SendToProgram(buf, &first);
5280       }
5281       StartClocks();
5282     }
5283     ModeHighlight();
5284   }
5285 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5286   /* Relay move to ICS or chess engine */
5287   if (appData.icsActive) {
5288     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5289         gameMode == IcsExamining) {
5290       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5291       ics_user_moved = 1;
5292     }
5293   } else {
5294     if (first.sendTime && (gameMode == BeginningOfGame ||
5295                            gameMode == MachinePlaysWhite ||
5296                            gameMode == MachinePlaysBlack)) {
5297       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5298     }
5299     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5300          // [HGM] book: if program might be playing, let it use book
5301         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5302         first.maybeThinking = TRUE;
5303     } else SendMoveToProgram(forwardMostMove-1, &first);
5304     if (currentMove == cmailOldMove + 1) {
5305       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5306     }
5307   }
5308
5309   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5310
5311   switch (gameMode) {
5312   case EditGame:
5313     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5314                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5315     case MT_NONE:
5316     case MT_CHECK:
5317       break;
5318     case MT_CHECKMATE:
5319     case MT_STAINMATE:
5320       if (WhiteOnMove(currentMove)) {
5321         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5322       } else {
5323         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5324       }
5325       break;
5326     case MT_STALEMATE:
5327       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5328       break;
5329     }
5330     break;
5331     
5332   case MachinePlaysBlack:
5333   case MachinePlaysWhite:
5334     /* disable certain menu options while machine is thinking */
5335     SetMachineThinkingEnables();
5336     break;
5337
5338   default:
5339     break;
5340   }
5341
5342   if(bookHit) { // [HGM] book: simulate book reply
5343         static char bookMove[MSG_SIZ]; // a bit generous?
5344
5345         programStats.nodes = programStats.depth = programStats.time = 
5346         programStats.score = programStats.got_only_move = 0;
5347         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5348
5349         strcpy(bookMove, "move ");
5350         strcat(bookMove, bookHit);
5351         HandleMachineMove(bookMove, &first);
5352   }
5353   return 1;
5354 }
5355
5356 void
5357 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5358      int fromX, fromY, toX, toY;
5359      int promoChar;
5360 {
5361     /* [HGM] This routine was added to allow calling of its two logical
5362        parts from other modules in the old way. Before, UserMoveEvent()
5363        automatically called FinishMove() if the move was OK, and returned
5364        otherwise. I separated the two, in order to make it possible to
5365        slip a promotion popup in between. But that it always needs two
5366        calls, to the first part, (now called UserMoveTest() ), and to
5367        FinishMove if the first part succeeded. Calls that do not need
5368        to do anything in between, can call this routine the old way. 
5369     */
5370     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
5371 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5372     if(moveType != ImpossibleMove)
5373         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5374 }
5375
5376 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5377 {
5378 //    char * hint = lastHint;
5379     FrontEndProgramStats stats;
5380
5381     stats.which = cps == &first ? 0 : 1;
5382     stats.depth = cpstats->depth;
5383     stats.nodes = cpstats->nodes;
5384     stats.score = cpstats->score;
5385     stats.time = cpstats->time;
5386     stats.pv = cpstats->movelist;
5387     stats.hint = lastHint;
5388     stats.an_move_index = 0;
5389     stats.an_move_count = 0;
5390
5391     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5392         stats.hint = cpstats->move_name;
5393         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5394         stats.an_move_count = cpstats->nr_moves;
5395     }
5396
5397     SetProgramStats( &stats );
5398 }
5399
5400 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5401 {   // [HGM] book: this routine intercepts moves to simulate book replies
5402     char *bookHit = NULL;
5403
5404     //first determine if the incoming move brings opponent into his book
5405     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5406         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5407     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5408     if(bookHit != NULL && !cps->bookSuspend) {
5409         // make sure opponent is not going to reply after receiving move to book position
5410         SendToProgram("force\n", cps);
5411         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5412     }
5413     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5414     // now arrange restart after book miss
5415     if(bookHit) {
5416         // after a book hit we never send 'go', and the code after the call to this routine
5417         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5418         char buf[MSG_SIZ];
5419         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5420         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5421         SendToProgram(buf, cps);
5422         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5423     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5424         SendToProgram("go\n", cps);
5425         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5426     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5427         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5428             SendToProgram("go\n", cps); 
5429         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5430     }
5431     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5432 }
5433
5434 char *savedMessage;
5435 ChessProgramState *savedState;
5436 void DeferredBookMove(void)
5437 {
5438         if(savedState->lastPing != savedState->lastPong)
5439                     ScheduleDelayedEvent(DeferredBookMove, 10);
5440         else
5441         HandleMachineMove(savedMessage, savedState);
5442 }
5443
5444 void
5445 HandleMachineMove(message, cps)
5446      char *message;
5447      ChessProgramState *cps;
5448 {
5449     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5450     char realname[MSG_SIZ];
5451     int fromX, fromY, toX, toY;
5452     ChessMove moveType;
5453     char promoChar;
5454     char *p;
5455     int machineWhite;
5456     char *bookHit;
5457
5458 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5459     /*
5460      * Kludge to ignore BEL characters
5461      */
5462     while (*message == '\007') message++;
5463
5464     /*
5465      * [HGM] engine debug message: ignore lines starting with '#' character
5466      */
5467     if(cps->debug && *message == '#') return;
5468
5469     /*
5470      * Look for book output
5471      */
5472     if (cps == &first && bookRequested) {
5473         if (message[0] == '\t' || message[0] == ' ') {
5474             /* Part of the book output is here; append it */
5475             strcat(bookOutput, message);
5476             strcat(bookOutput, "  \n");
5477             return;
5478         } else if (bookOutput[0] != NULLCHAR) {
5479             /* All of book output has arrived; display it */
5480             char *p = bookOutput;
5481             while (*p != NULLCHAR) {
5482                 if (*p == '\t') *p = ' ';
5483                 p++;
5484             }
5485             DisplayInformation(bookOutput);
5486             bookRequested = FALSE;
5487             /* Fall through to parse the current output */
5488         }
5489     }
5490
5491     /*
5492      * Look for machine move.
5493      */
5494     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5495         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5496     {
5497         /* This method is only useful on engines that support ping */
5498         if (cps->lastPing != cps->lastPong) {
5499           if (gameMode == BeginningOfGame) {
5500             /* Extra move from before last new; ignore */
5501             if (appData.debugMode) {
5502                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5503             }
5504           } else {
5505             if (appData.debugMode) {
5506                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5507                         cps->which, gameMode);
5508             }
5509
5510             SendToProgram("undo\n", cps);
5511           }
5512           return;
5513         }
5514
5515         switch (gameMode) {
5516           case BeginningOfGame:
5517             /* Extra move from before last reset; ignore */
5518             if (appData.debugMode) {
5519                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5520             }
5521             return;
5522
5523           case EndOfGame:
5524           case IcsIdle:
5525           default:
5526             /* Extra move after we tried to stop.  The mode test is
5527                not a reliable way of detecting this problem, but it's
5528                the best we can do on engines that don't support ping.
5529             */
5530             if (appData.debugMode) {
5531                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5532                         cps->which, gameMode);
5533             }
5534             SendToProgram("undo\n", cps);
5535             return;
5536
5537           case MachinePlaysWhite:
5538           case IcsPlayingWhite:
5539             machineWhite = TRUE;
5540             break;
5541
5542           case MachinePlaysBlack:
5543           case IcsPlayingBlack:
5544             machineWhite = FALSE;
5545             break;
5546
5547           case TwoMachinesPlay:
5548             machineWhite = (cps->twoMachinesColor[0] == 'w');
5549             break;
5550         }
5551         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5552             if (appData.debugMode) {
5553                 fprintf(debugFP,
5554                         "Ignoring move out of turn by %s, gameMode %d"
5555                         ", forwardMost %d\n",
5556                         cps->which, gameMode, forwardMostMove);
5557             }
5558             return;
5559         }
5560
5561     if (appData.debugMode) { int f = forwardMostMove;
5562         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5563                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5564     }
5565         if(cps->alphaRank) AlphaRank(machineMove, 4);
5566         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5567                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5568             /* Machine move could not be parsed; ignore it. */
5569             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5570                     machineMove, cps->which);
5571             DisplayError(buf1, 0);
5572             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5573                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5574             if (gameMode == TwoMachinesPlay) {
5575               GameEnds(machineWhite ? BlackWins : WhiteWins,
5576                        buf1, GE_XBOARD);
5577             }
5578             return;
5579         }
5580
5581         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5582         /* So we have to redo legality test with true e.p. status here,  */
5583         /* to make sure an illegal e.p. capture does not slip through,   */
5584         /* to cause a forfeit on a justified illegal-move complaint      */
5585         /* of the opponent.                                              */
5586         if( gameMode==TwoMachinesPlay && appData.testLegality
5587             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5588                                                               ) {
5589            ChessMove moveType;
5590            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5591                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5592                              fromY, fromX, toY, toX, promoChar);
5593             if (appData.debugMode) {
5594                 int i;
5595                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5596                     castlingRights[forwardMostMove][i], castlingRank[i]);
5597                 fprintf(debugFP, "castling rights\n");
5598             }
5599             if(moveType == IllegalMove) {
5600                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5601                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5602                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5603                            buf1, GE_XBOARD);
5604                 return;
5605            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5606            /* [HGM] Kludge to handle engines that send FRC-style castling
5607               when they shouldn't (like TSCP-Gothic) */
5608            switch(moveType) {
5609              case WhiteASideCastleFR:
5610              case BlackASideCastleFR:
5611                toX+=2;
5612                currentMoveString[2]++;
5613                break;
5614              case WhiteHSideCastleFR:
5615              case BlackHSideCastleFR:
5616                toX--;
5617                currentMoveString[2]--;
5618                break;
5619              default: ; // nothing to do, but suppresses warning of pedantic compilers
5620            }
5621         }
5622         hintRequested = FALSE;
5623         lastHint[0] = NULLCHAR;
5624         bookRequested = FALSE;
5625         /* Program may be pondering now */
5626         cps->maybeThinking = TRUE;
5627         if (cps->sendTime == 2) cps->sendTime = 1;
5628         if (cps->offeredDraw) cps->offeredDraw--;
5629
5630 #if ZIPPY
5631         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5632             first.initDone) {
5633           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5634           ics_user_moved = 1;
5635           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5636                 char buf[3*MSG_SIZ];
5637
5638                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %.0f nodes, %1.0f knps) PV=%s\n",
5639                         programStats.score / 100.,
5640                         programStats.depth,
5641                         programStats.time / 100.,
5642                         u64ToDouble(programStats.nodes),
5643                         u64ToDouble(programStats.nodes) / (10*abs(programStats.time) + 1.),
5644                         programStats.movelist);
5645                 SendToICS(buf);
5646           }
5647         }
5648 #endif
5649         /* currentMoveString is set as a side-effect of ParseOneMove */
5650         strcpy(machineMove, currentMoveString);
5651         strcat(machineMove, "\n");
5652         strcpy(moveList[forwardMostMove], machineMove);
5653
5654         /* [AS] Save move info and clear stats for next move */
5655         pvInfoList[ forwardMostMove ].score = programStats.score;
5656         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5657         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5658         ClearProgramStats();
5659         thinkOutput[0] = NULLCHAR;
5660         hiddenThinkOutputState = 0;
5661
5662         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5663
5664         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5665         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5666             int count = 0;
5667
5668             while( count < adjudicateLossPlies ) {
5669                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5670
5671                 if( count & 1 ) {
5672                     score = -score; /* Flip score for winning side */
5673                 }
5674
5675                 if( score > adjudicateLossThreshold ) {
5676                     break;
5677                 }
5678
5679                 count++;
5680             }
5681
5682             if( count >= adjudicateLossPlies ) {
5683                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5684
5685                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5686                     "Xboard adjudication", 
5687                     GE_XBOARD );
5688
5689                 return;
5690             }
5691         }
5692
5693         if( gameMode == TwoMachinesPlay ) {
5694           // [HGM] some adjudications useful with buggy engines
5695             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5696           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5697
5698
5699             if( appData.testLegality )
5700             {   /* [HGM] Some more adjudications for obstinate engines */
5701                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5702                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5703                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5704                 static int moveCount = 6;
5705                 ChessMove result;
5706                 char *reason = NULL;
5707
5708                 /* Count what is on board. */
5709                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5710                 {   ChessSquare p = boards[forwardMostMove][i][j];
5711                     int m=i;
5712
5713                     switch((int) p)
5714                     {   /* count B,N,R and other of each side */
5715                         case WhiteKing:
5716                         case BlackKing:
5717                              NrK++; break; // [HGM] atomic: count Kings
5718                         case WhiteKnight:
5719                              NrWN++; break;
5720                         case WhiteBishop:
5721                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5722                              bishopsColor |= 1 << ((i^j)&1);
5723                              NrWB++; break;
5724                         case BlackKnight:
5725                              NrBN++; break;
5726                         case BlackBishop:
5727                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5728                              bishopsColor |= 1 << ((i^j)&1);
5729                              NrBB++; break;
5730                         case WhiteRook:
5731                              NrWR++; break;
5732                         case BlackRook:
5733                              NrBR++; break;
5734                         case WhiteQueen:
5735                              NrWQ++; break;
5736                         case BlackQueen:
5737                              NrBQ++; break;
5738                         case EmptySquare: 
5739                              break;
5740                         case BlackPawn:
5741                              m = 7-i;
5742                         case WhitePawn:
5743                              PawnAdvance += m; NrPawns++;
5744                     }
5745                     NrPieces += (p != EmptySquare);
5746                     NrW += ((int)p < (int)BlackPawn);
5747                     if(gameInfo.variant == VariantXiangqi && 
5748                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5749                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5750                         NrW -= ((int)p < (int)BlackPawn);
5751                     }
5752                 }
5753
5754                 /* Some material-based adjudications that have to be made before stalemate test */
5755                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5756                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5757                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5758                      if(appData.checkMates) {
5759                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5760                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5761                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
5762                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
5763                          return;
5764                      }
5765                 }
5766
5767                 /* Bare King in Shatranj (loses) or Losers (wins) */
5768                 if( NrW == 1 || NrPieces - NrW == 1) {
5769                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5770                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
5771                      if(appData.checkMates) {
5772                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5773                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5774                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5775                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5776                          return;
5777                      }
5778                   } else
5779                   if( gameInfo.variant == VariantShatranj && --bare < 0)
5780                   {    /* bare King */
5781                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5782                         if(appData.checkMates) {
5783                             /* but only adjudicate if adjudication enabled */
5784                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5785                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5786                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
5787                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5788                             return;
5789                         }
5790                   }
5791                 } else bare = 1;
5792
5793
5794             // don't wait for engine to announce game end if we can judge ourselves
5795             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5796                                        castlingRights[forwardMostMove]) ) {
5797               case MT_CHECK:
5798                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5799                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
5800                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5801                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5802                             checkCnt++;
5803                         if(checkCnt >= 2) {
5804                             reason = "Xboard adjudication: 3rd check";
5805                             epStatus[forwardMostMove] = EP_CHECKMATE;
5806                             break;
5807                         }
5808                     }
5809                 }
5810               case MT_NONE:
5811               default:
5812                 break;
5813               case MT_STALEMATE:
5814               case MT_STAINMATE:
5815                 reason = "Xboard adjudication: Stalemate";
5816                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5817                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
5818                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5819                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
5820                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5821                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5822                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5823                                                                         EP_CHECKMATE : EP_WINS);
5824                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5825                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5826                 }
5827                 break;
5828               case MT_CHECKMATE:
5829                 reason = "Xboard adjudication: Checkmate";
5830                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5831                 break;
5832             }
5833
5834                 switch(i = epStatus[forwardMostMove]) {
5835                     case EP_STALEMATE:
5836                         result = GameIsDrawn; break;
5837                     case EP_CHECKMATE:
5838                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5839                     case EP_WINS:
5840                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5841                     default:
5842                         result = (ChessMove) 0;
5843                 }
5844                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5845                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5846                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5847                     GameEnds( result, reason, GE_XBOARD );
5848                     return;
5849                 }
5850
5851                 /* Next absolutely insufficient mating material. */
5852                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
5853                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5854                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5855                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5856                 {    /* KBK, KNK, KK of KBKB with like Bishops */
5857
5858                      /* always flag draws, for judging claims */
5859                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
5860
5861                      if(appData.materialDraws) {
5862                          /* but only adjudicate them if adjudication enabled */
5863                          SendToProgram("force\n", cps->other); // suppress reply
5864                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5865                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5866                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5867                          return;
5868                      }
5869                 }
5870
5871                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5872                 if(NrPieces == 4 && 
5873                    (   NrWR == 1 && NrBR == 1 /* KRKR */
5874                    || NrWQ==1 && NrBQ==1     /* KQKQ */
5875                    || NrWN==2 || NrBN==2     /* KNNK */
5876                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5877                   ) ) {
5878                      if(--moveCount < 0 && appData.trivialDraws)
5879                      {    /* if the first 3 moves do not show a tactical win, declare draw */
5880                           SendToProgram("force\n", cps->other); // suppress reply
5881                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5882                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5883                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5884                           return;
5885                      }
5886                 } else moveCount = 6;
5887             }
5888           }
5889 #if 1
5890     if (appData.debugMode) { int i;
5891       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5892               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5893               appData.drawRepeats);
5894       for( i=forwardMostMove; i>=backwardMostMove; i-- )
5895            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5896
5897     }
5898 #endif
5899                 /* Check for rep-draws */
5900                 count = 0;
5901                 for(k = forwardMostMove-2;
5902                     k>=backwardMostMove && k>=forwardMostMove-100 &&
5903                         epStatus[k] < EP_UNKNOWN &&
5904                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5905                     k-=2)
5906                 {   int rights=0;
5907 #if 0
5908     if (appData.debugMode) {
5909       fprintf(debugFP, " loop\n");
5910     }
5911 #endif
5912                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
5913 #if 0
5914     if (appData.debugMode) {
5915       fprintf(debugFP, "match\n");
5916     }
5917 #endif
5918                         /* compare castling rights */
5919                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5920                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5921                                 rights++; /* King lost rights, while rook still had them */
5922                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5923                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5924                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5925                                    rights++; /* but at least one rook lost them */
5926                         }
5927                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5928                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5929                                 rights++; 
5930                         if( castlingRights[forwardMostMove][5] >= 0 ) {
5931                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5932                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5933                                    rights++;
5934                         }
5935 #if 0
5936     if (appData.debugMode) {
5937       for(i=0; i<nrCastlingRights; i++)
5938       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
5939     }
5940
5941     if (appData.debugMode) {
5942       fprintf(debugFP, " %d %d\n", rights, k);
5943     }
5944 #endif
5945                         if( rights == 0 && ++count > appData.drawRepeats-2
5946                             && appData.drawRepeats > 1) {
5947                              /* adjudicate after user-specified nr of repeats */
5948                              SendToProgram("force\n", cps->other); // suppress reply
5949                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5950                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5951                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
5952                                 // [HGM] xiangqi: check for forbidden perpetuals
5953                                 int m, ourPerpetual = 1, hisPerpetual = 1;
5954                                 for(m=forwardMostMove; m>k; m-=2) {
5955                                     if(MateTest(boards[m], PosFlags(m), 
5956                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
5957                                         ourPerpetual = 0; // the current mover did not always check
5958                                     if(MateTest(boards[m-1], PosFlags(m-1), 
5959                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
5960                                         hisPerpetual = 0; // the opponent did not always check
5961                                 }
5962                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
5963                                                                         ourPerpetual, hisPerpetual);
5964                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
5965                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5966                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
5967                                     return;
5968                                 }
5969                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
5970                                     break; // (or we would have caught him before). Abort repetition-checking loop.
5971                                 // Now check for perpetual chases
5972                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
5973                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
5974                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
5975                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
5976                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5977                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
5978                                         return;
5979                                     }
5980                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
5981                                         break; // Abort repetition-checking loop.
5982                                 }
5983                                 // if neither of us is checking or chasing all the time, or both are, it is draw
5984                              }
5985                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
5986                              return;
5987                         }
5988                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
5989                              epStatus[forwardMostMove] = EP_REP_DRAW;
5990                     }
5991                 }
5992
5993                 /* Now we test for 50-move draws. Determine ply count */
5994                 count = forwardMostMove;
5995                 /* look for last irreversble move */
5996                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
5997                     count--;
5998                 /* if we hit starting position, add initial plies */
5999                 if( count == backwardMostMove )
6000                     count -= initialRulePlies;
6001                 count = forwardMostMove - count; 
6002                 if( count >= 100)
6003                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6004                          /* this is used to judge if draw claims are legal */
6005                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6006                          SendToProgram("force\n", cps->other); // suppress reply
6007                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6008                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6009                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6010                          return;
6011                 }
6012
6013                 /* if draw offer is pending, treat it as a draw claim
6014                  * when draw condition present, to allow engines a way to
6015                  * claim draws before making their move to avoid a race
6016                  * condition occurring after their move
6017                  */
6018                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6019                          char *p = NULL;
6020                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6021                              p = "Draw claim: 50-move rule";
6022                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6023                              p = "Draw claim: 3-fold repetition";
6024                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6025                              p = "Draw claim: insufficient mating material";
6026                          if( p != NULL ) {
6027                              SendToProgram("force\n", cps->other); // suppress reply
6028                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6029                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6030                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6031                              return;
6032                          }
6033                 }
6034
6035
6036                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6037                     SendToProgram("force\n", cps->other); // suppress reply
6038                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6039                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6040
6041                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6042
6043                     return;
6044                 }
6045         }
6046
6047         bookHit = NULL;
6048         if (gameMode == TwoMachinesPlay) {
6049             /* [HGM] relaying draw offers moved to after reception of move */
6050             /* and interpreting offer as claim if it brings draw condition */
6051             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6052                 SendToProgram("draw\n", cps->other);
6053             }
6054             if (cps->other->sendTime) {
6055                 SendTimeRemaining(cps->other,
6056                                   cps->other->twoMachinesColor[0] == 'w');
6057             }
6058             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6059             if (firstMove && !bookHit) {
6060                 firstMove = FALSE;
6061                 if (cps->other->useColors) {
6062                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6063                 }
6064                 SendToProgram("go\n", cps->other);
6065             }
6066             cps->other->maybeThinking = TRUE;
6067         }
6068
6069         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6070         
6071         if (!pausing && appData.ringBellAfterMoves) {
6072             RingBell();
6073         }
6074
6075         /* 
6076          * Reenable menu items that were disabled while
6077          * machine was thinking
6078          */
6079         if (gameMode != TwoMachinesPlay)
6080             SetUserThinkingEnables();
6081
6082         // [HGM] book: after book hit opponent has received move and is now in force mode
6083         // force the book reply into it, and then fake that it outputted this move by jumping
6084         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6085         if(bookHit) {
6086                 static char bookMove[MSG_SIZ]; // a bit generous?
6087
6088                 strcpy(bookMove, "move ");
6089                 strcat(bookMove, bookHit);
6090                 message = bookMove;
6091                 cps = cps->other;
6092                 programStats.nodes = programStats.depth = programStats.time = 
6093                 programStats.score = programStats.got_only_move = 0;
6094                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6095
6096                 if(cps->lastPing != cps->lastPong) {
6097                     savedMessage = message; // args for deferred call
6098                     savedState = cps;
6099                     ScheduleDelayedEvent(DeferredBookMove, 10);
6100                     return;
6101                 }
6102                 goto FakeBookMove;
6103         }
6104
6105         return;
6106     }
6107
6108     /* Set special modes for chess engines.  Later something general
6109      *  could be added here; for now there is just one kludge feature,
6110      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6111      *  when "xboard" is given as an interactive command.
6112      */
6113     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6114         cps->useSigint = FALSE;
6115         cps->useSigterm = FALSE;
6116     }
6117
6118     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6119      * want this, I was asked to put it in, and obliged.
6120      */
6121     if (!strncmp(message, "setboard ", 9)) {
6122         Board initial_position; int i;
6123
6124         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6125
6126         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6127             DisplayError(_("Bad FEN received from engine"), 0);
6128             return ;
6129         } else {
6130            Reset(FALSE, FALSE);
6131            CopyBoard(boards[0], initial_position);
6132            initialRulePlies = FENrulePlies;
6133            epStatus[0] = FENepStatus;
6134            for( i=0; i<nrCastlingRights; i++ )
6135                 castlingRights[0][i] = FENcastlingRights[i];
6136            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6137            else gameMode = MachinePlaysBlack;                 
6138            DrawPosition(FALSE, boards[currentMove]);
6139         }
6140         return;
6141     }
6142
6143     /*
6144      * Look for communication commands
6145      */
6146     if (!strncmp(message, "telluser ", 9)) {
6147         DisplayNote(message + 9);
6148         return;
6149     }
6150     if (!strncmp(message, "tellusererror ", 14)) {
6151         DisplayError(message + 14, 0);
6152         return;
6153     }
6154     if (!strncmp(message, "tellopponent ", 13)) {
6155       if (appData.icsActive) {
6156         if (loggedOn) {
6157           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6158           SendToICS(buf1);
6159         }
6160       } else {
6161         DisplayNote(message + 13);
6162       }
6163       return;
6164     }
6165     if (!strncmp(message, "tellothers ", 11)) {
6166       if (appData.icsActive) {
6167         if (loggedOn) {
6168           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6169           SendToICS(buf1);
6170         }
6171       }
6172       return;
6173     }
6174     if (!strncmp(message, "tellall ", 8)) {
6175       if (appData.icsActive) {
6176         if (loggedOn) {
6177           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6178           SendToICS(buf1);
6179         }
6180       } else {
6181         DisplayNote(message + 8);
6182       }
6183       return;
6184     }
6185     if (strncmp(message, "warning", 7) == 0) {
6186         /* Undocumented feature, use tellusererror in new code */
6187         DisplayError(message, 0);
6188         return;
6189     }
6190     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6191         strcpy(realname, cps->tidy);
6192         strcat(realname, " query");
6193         AskQuestion(realname, buf2, buf1, cps->pr);
6194         return;
6195     }
6196     /* Commands from the engine directly to ICS.  We don't allow these to be 
6197      *  sent until we are logged on. Crafty kibitzes have been known to 
6198      *  interfere with the login process.
6199      */
6200     if (loggedOn) {
6201         if (!strncmp(message, "tellics ", 8)) {
6202             SendToICS(message + 8);
6203             SendToICS("\n");
6204             return;
6205         }
6206         if (!strncmp(message, "tellicsnoalias ", 15)) {
6207             SendToICS(ics_prefix);
6208             SendToICS(message + 15);
6209             SendToICS("\n");
6210             return;
6211         }
6212         /* The following are for backward compatibility only */
6213         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6214             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6215             SendToICS(ics_prefix);
6216             SendToICS(message);
6217             SendToICS("\n");
6218             return;
6219         }
6220     }
6221     if (strncmp(message, "feature ", 8) == 0) {
6222       ParseFeatures(message+8, cps);
6223     }
6224     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6225         return;
6226     }
6227     /*
6228      * If the move is illegal, cancel it and redraw the board.
6229      * Also deal with other error cases.  Matching is rather loose
6230      * here to accommodate engines written before the spec.
6231      */
6232     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6233         strncmp(message, "Error", 5) == 0) {
6234         if (StrStr(message, "name") || 
6235             StrStr(message, "rating") || StrStr(message, "?") ||
6236             StrStr(message, "result") || StrStr(message, "board") ||
6237             StrStr(message, "bk") || StrStr(message, "computer") ||
6238             StrStr(message, "variant") || StrStr(message, "hint") ||
6239             StrStr(message, "random") || StrStr(message, "depth") ||
6240             StrStr(message, "accepted")) {
6241             return;
6242         }
6243         if (StrStr(message, "protover")) {
6244           /* Program is responding to input, so it's apparently done
6245              initializing, and this error message indicates it is
6246              protocol version 1.  So we don't need to wait any longer
6247              for it to initialize and send feature commands. */
6248           FeatureDone(cps, 1);
6249           cps->protocolVersion = 1;
6250           return;
6251         }
6252         cps->maybeThinking = FALSE;
6253
6254         if (StrStr(message, "draw")) {
6255             /* Program doesn't have "draw" command */
6256             cps->sendDrawOffers = 0;
6257             return;
6258         }
6259         if (cps->sendTime != 1 &&
6260             (StrStr(message, "time") || StrStr(message, "otim"))) {
6261           /* Program apparently doesn't have "time" or "otim" command */
6262           cps->sendTime = 0;
6263           return;
6264         }
6265         if (StrStr(message, "analyze")) {
6266             cps->analysisSupport = FALSE;
6267             cps->analyzing = FALSE;
6268             Reset(FALSE, TRUE);
6269             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6270             DisplayError(buf2, 0);
6271             return;
6272         }
6273         if (StrStr(message, "(no matching move)st")) {
6274           /* Special kludge for GNU Chess 4 only */
6275           cps->stKludge = TRUE;
6276           SendTimeControl(cps, movesPerSession, timeControl,
6277                           timeIncrement, appData.searchDepth,
6278                           searchTime);
6279           return;
6280         }
6281         if (StrStr(message, "(no matching move)sd")) {
6282           /* Special kludge for GNU Chess 4 only */
6283           cps->sdKludge = TRUE;
6284           SendTimeControl(cps, movesPerSession, timeControl,
6285                           timeIncrement, appData.searchDepth,
6286                           searchTime);
6287           return;
6288         }
6289         if (!StrStr(message, "llegal")) {
6290             return;
6291         }
6292         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6293             gameMode == IcsIdle) return;
6294         if (forwardMostMove <= backwardMostMove) return;
6295 #if 0
6296         /* Following removed: it caused a bug where a real illegal move
6297            message in analyze mored would be ignored. */
6298         if (cps == &first && programStats.ok_to_send == 0) {
6299             /* Bogus message from Crafty responding to "."  This filtering
6300                can miss some of the bad messages, but fortunately the bug 
6301                is fixed in current Crafty versions, so it doesn't matter. */
6302             return;
6303         }
6304 #endif
6305         if (pausing) PauseEvent();
6306         if (gameMode == PlayFromGameFile) {
6307             /* Stop reading this game file */
6308             gameMode = EditGame;
6309             ModeHighlight();
6310         }
6311         currentMove = --forwardMostMove;
6312         DisplayMove(currentMove-1); /* before DisplayMoveError */
6313         SwitchClocks();
6314         DisplayBothClocks();
6315         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6316                 parseList[currentMove], cps->which);
6317         DisplayMoveError(buf1);
6318         DrawPosition(FALSE, boards[currentMove]);
6319
6320         /* [HGM] illegal-move claim should forfeit game when Xboard */
6321         /* only passes fully legal moves                            */
6322         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6323             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6324                                 "False illegal-move claim", GE_XBOARD );
6325         }
6326         return;
6327     }
6328     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6329         /* Program has a broken "time" command that
6330            outputs a string not ending in newline.
6331            Don't use it. */
6332         cps->sendTime = 0;
6333     }
6334     
6335     /*
6336      * If chess program startup fails, exit with an error message.
6337      * Attempts to recover here are futile.
6338      */
6339     if ((StrStr(message, "unknown host") != NULL)
6340         || (StrStr(message, "No remote directory") != NULL)
6341         || (StrStr(message, "not found") != NULL)
6342         || (StrStr(message, "No such file") != NULL)
6343         || (StrStr(message, "can't alloc") != NULL)
6344         || (StrStr(message, "Permission denied") != NULL)) {
6345
6346         cps->maybeThinking = FALSE;
6347         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6348                 cps->which, cps->program, cps->host, message);
6349         RemoveInputSource(cps->isr);
6350         DisplayFatalError(buf1, 0, 1);
6351         return;
6352     }
6353     
6354     /* 
6355      * Look for hint output
6356      */
6357     if (sscanf(message, "Hint: %s", buf1) == 1) {
6358         if (cps == &first && hintRequested) {
6359             hintRequested = FALSE;
6360             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6361                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6362                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6363                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6364                                     fromY, fromX, toY, toX, promoChar, buf1);
6365                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6366                 DisplayInformation(buf2);
6367             } else {
6368                 /* Hint move could not be parsed!? */
6369               snprintf(buf2, sizeof(buf2),
6370                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6371                         buf1, cps->which);
6372                 DisplayError(buf2, 0);
6373             }
6374         } else {
6375             strcpy(lastHint, buf1);
6376         }
6377         return;
6378     }
6379
6380     /*
6381      * Ignore other messages if game is not in progress
6382      */
6383     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6384         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6385
6386     /*
6387      * look for win, lose, draw, or draw offer
6388      */
6389     if (strncmp(message, "1-0", 3) == 0) {
6390         char *p, *q, *r = "";
6391         p = strchr(message, '{');
6392         if (p) {
6393             q = strchr(p, '}');
6394             if (q) {
6395                 *q = NULLCHAR;
6396                 r = p + 1;
6397             }
6398         }
6399         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6400         return;
6401     } else if (strncmp(message, "0-1", 3) == 0) {
6402         char *p, *q, *r = "";
6403         p = strchr(message, '{');
6404         if (p) {
6405             q = strchr(p, '}');
6406             if (q) {
6407                 *q = NULLCHAR;
6408                 r = p + 1;
6409             }
6410         }
6411         /* Kludge for Arasan 4.1 bug */
6412         if (strcmp(r, "Black resigns") == 0) {
6413             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6414             return;
6415         }
6416         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6417         return;
6418     } else if (strncmp(message, "1/2", 3) == 0) {
6419         char *p, *q, *r = "";
6420         p = strchr(message, '{');
6421         if (p) {
6422             q = strchr(p, '}');
6423             if (q) {
6424                 *q = NULLCHAR;
6425                 r = p + 1;
6426             }
6427         }
6428             
6429         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6430         return;
6431
6432     } else if (strncmp(message, "White resign", 12) == 0) {
6433         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6434         return;
6435     } else if (strncmp(message, "Black resign", 12) == 0) {
6436         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6437         return;
6438     } else if (strncmp(message, "White matches", 13) == 0 ||
6439                strncmp(message, "Black matches", 13) == 0   ) {
6440         /* [HGM] ignore GNUShogi noises */
6441         return;
6442     } else if (strncmp(message, "White", 5) == 0 &&
6443                message[5] != '(' &&
6444                StrStr(message, "Black") == NULL) {
6445         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6446         return;
6447     } else if (strncmp(message, "Black", 5) == 0 &&
6448                message[5] != '(') {
6449         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6450         return;
6451     } else if (strcmp(message, "resign") == 0 ||
6452                strcmp(message, "computer resigns") == 0) {
6453         switch (gameMode) {
6454           case MachinePlaysBlack:
6455           case IcsPlayingBlack:
6456             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6457             break;
6458           case MachinePlaysWhite:
6459           case IcsPlayingWhite:
6460             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6461             break;
6462           case TwoMachinesPlay:
6463             if (cps->twoMachinesColor[0] == 'w')
6464               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6465             else
6466               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6467             break;
6468           default:
6469             /* can't happen */
6470             break;
6471         }
6472         return;
6473     } else if (strncmp(message, "opponent mates", 14) == 0) {
6474         switch (gameMode) {
6475           case MachinePlaysBlack:
6476           case IcsPlayingBlack:
6477             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6478             break;
6479           case MachinePlaysWhite:
6480           case IcsPlayingWhite:
6481             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6482             break;
6483           case TwoMachinesPlay:
6484             if (cps->twoMachinesColor[0] == 'w')
6485               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6486             else
6487               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6488             break;
6489           default:
6490             /* can't happen */
6491             break;
6492         }
6493         return;
6494     } else if (strncmp(message, "computer mates", 14) == 0) {
6495         switch (gameMode) {
6496           case MachinePlaysBlack:
6497           case IcsPlayingBlack:
6498             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6499             break;
6500           case MachinePlaysWhite:
6501           case IcsPlayingWhite:
6502             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6503             break;
6504           case TwoMachinesPlay:
6505             if (cps->twoMachinesColor[0] == 'w')
6506               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6507             else
6508               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6509             break;
6510           default:
6511             /* can't happen */
6512             break;
6513         }
6514         return;
6515     } else if (strncmp(message, "checkmate", 9) == 0) {
6516         if (WhiteOnMove(forwardMostMove)) {
6517             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6518         } else {
6519             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6520         }
6521         return;
6522     } else if (strstr(message, "Draw") != NULL ||
6523                strstr(message, "game is a draw") != NULL) {
6524         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6525         return;
6526     } else if (strstr(message, "offer") != NULL &&
6527                strstr(message, "draw") != NULL) {
6528 #if ZIPPY
6529         if (appData.zippyPlay && first.initDone) {
6530             /* Relay offer to ICS */
6531             SendToICS(ics_prefix);
6532             SendToICS("draw\n");
6533         }
6534 #endif
6535         cps->offeredDraw = 2; /* valid until this engine moves twice */
6536         if (gameMode == TwoMachinesPlay) {
6537             if (cps->other->offeredDraw) {
6538                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6539             /* [HGM] in two-machine mode we delay relaying draw offer      */
6540             /* until after we also have move, to see if it is really claim */
6541             }
6542 #if 0
6543               else {
6544                 if (cps->other->sendDrawOffers) {
6545                     SendToProgram("draw\n", cps->other);
6546                 }
6547             }
6548 #endif
6549         } else if (gameMode == MachinePlaysWhite ||
6550                    gameMode == MachinePlaysBlack) {
6551           if (userOfferedDraw) {
6552             DisplayInformation(_("Machine accepts your draw offer"));
6553             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6554           } else {
6555             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6556           }
6557         }
6558     }
6559
6560     
6561     /*
6562      * Look for thinking output
6563      */
6564     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6565           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6566                                 ) {
6567         int plylev, mvleft, mvtot, curscore, time;
6568         char mvname[MOVE_LEN];
6569         u64 nodes; // [DM]
6570         char plyext;
6571         int ignore = FALSE;
6572         int prefixHint = FALSE;
6573         mvname[0] = NULLCHAR;
6574
6575         switch (gameMode) {
6576           case MachinePlaysBlack:
6577           case IcsPlayingBlack:
6578             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6579             break;
6580           case MachinePlaysWhite:
6581           case IcsPlayingWhite:
6582             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6583             break;
6584           case AnalyzeMode:
6585           case AnalyzeFile:
6586             break;
6587           case IcsObserving: /* [DM] icsEngineAnalyze */
6588             if (!appData.icsEngineAnalyze) ignore = TRUE;
6589             break;
6590           case TwoMachinesPlay:
6591             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6592                 ignore = TRUE;
6593             }
6594             break;
6595           default:
6596             ignore = TRUE;
6597             break;
6598         }
6599
6600         if (!ignore) {
6601             buf1[0] = NULLCHAR;
6602             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6603                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6604
6605                 if (plyext != ' ' && plyext != '\t') {
6606                     time *= 100;
6607                 }
6608
6609                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6610                 if( cps->scoreIsAbsolute && 
6611                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6612                 {
6613                     curscore = -curscore;
6614                 }
6615
6616
6617                 programStats.depth = plylev;
6618                 programStats.nodes = nodes;
6619                 programStats.time = time;
6620                 programStats.score = curscore;
6621                 programStats.got_only_move = 0;
6622
6623                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6624                         int ticklen;
6625
6626                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6627                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6628                         if(WhiteOnMove(forwardMostMove)) 
6629                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6630                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6631                 }
6632
6633                 /* Buffer overflow protection */
6634                 if (buf1[0] != NULLCHAR) {
6635                     if (strlen(buf1) >= sizeof(programStats.movelist)
6636                         && appData.debugMode) {
6637                         fprintf(debugFP,
6638                                 "PV is too long; using the first %d bytes.\n",
6639                                 sizeof(programStats.movelist) - 1);
6640                     }
6641
6642                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6643                 } else {
6644                     sprintf(programStats.movelist, " no PV\n");
6645                 }
6646
6647                 if (programStats.seen_stat) {
6648                     programStats.ok_to_send = 1;
6649                 }
6650
6651                 if (strchr(programStats.movelist, '(') != NULL) {
6652                     programStats.line_is_book = 1;
6653                     programStats.nr_moves = 0;
6654                     programStats.moves_left = 0;
6655                 } else {
6656                     programStats.line_is_book = 0;
6657                 }
6658
6659                 SendProgramStatsToFrontend( cps, &programStats );
6660
6661                 /* 
6662                     [AS] Protect the thinkOutput buffer from overflow... this
6663                     is only useful if buf1 hasn't overflowed first!
6664                 */
6665                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6666                         plylev, 
6667                         (gameMode == TwoMachinesPlay ?
6668                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6669                         ((double) curscore) / 100.0,
6670                         prefixHint ? lastHint : "",
6671                         prefixHint ? " " : "" );
6672
6673                 if( buf1[0] != NULLCHAR ) {
6674                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6675
6676                     if( strlen(buf1) > max_len ) {
6677                         if( appData.debugMode) {
6678                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6679                         }
6680                         buf1[max_len+1] = '\0';
6681                     }
6682
6683                     strcat( thinkOutput, buf1 );
6684                 }
6685
6686                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6687                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6688                     DisplayMove(currentMove - 1);
6689                     DisplayAnalysis();
6690                 }
6691                 return;
6692
6693             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6694                 /* crafty (9.25+) says "(only move) <move>"
6695                  * if there is only 1 legal move
6696                  */
6697                 sscanf(p, "(only move) %s", buf1);
6698                 sprintf(thinkOutput, "%s (only move)", buf1);
6699                 sprintf(programStats.movelist, "%s (only move)", buf1);
6700                 programStats.depth = 1;
6701                 programStats.nr_moves = 1;
6702                 programStats.moves_left = 1;
6703                 programStats.nodes = 1;
6704                 programStats.time = 1;
6705                 programStats.got_only_move = 1;
6706
6707                 /* Not really, but we also use this member to
6708                    mean "line isn't going to change" (Crafty
6709                    isn't searching, so stats won't change) */
6710                 programStats.line_is_book = 1;
6711
6712                 SendProgramStatsToFrontend( cps, &programStats );
6713                 
6714                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
6715                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6716                     DisplayMove(currentMove - 1);
6717                     DisplayAnalysis();
6718                 }
6719                 return;
6720             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6721                               &time, &nodes, &plylev, &mvleft,
6722                               &mvtot, mvname) >= 5) {
6723                 /* The stat01: line is from Crafty (9.29+) in response
6724                    to the "." command */
6725                 programStats.seen_stat = 1;
6726                 cps->maybeThinking = TRUE;
6727
6728                 if (programStats.got_only_move || !appData.periodicUpdates)
6729                   return;
6730
6731                 programStats.depth = plylev;
6732                 programStats.time = time;
6733                 programStats.nodes = nodes;
6734                 programStats.moves_left = mvleft;
6735                 programStats.nr_moves = mvtot;
6736                 strcpy(programStats.move_name, mvname);
6737                 programStats.ok_to_send = 1;
6738                 programStats.movelist[0] = '\0';
6739
6740                 SendProgramStatsToFrontend( cps, &programStats );
6741
6742                 DisplayAnalysis();
6743                 return;
6744
6745             } else if (strncmp(message,"++",2) == 0) {
6746                 /* Crafty 9.29+ outputs this */
6747                 programStats.got_fail = 2;
6748                 return;
6749
6750             } else if (strncmp(message,"--",2) == 0) {
6751                 /* Crafty 9.29+ outputs this */
6752                 programStats.got_fail = 1;
6753                 return;
6754
6755             } else if (thinkOutput[0] != NULLCHAR &&
6756                        strncmp(message, "    ", 4) == 0) {
6757                 unsigned message_len;
6758
6759                 p = message;
6760                 while (*p && *p == ' ') p++;
6761
6762                 message_len = strlen( p );
6763
6764                 /* [AS] Avoid buffer overflow */
6765                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6766                     strcat(thinkOutput, " ");
6767                     strcat(thinkOutput, p);
6768                 }
6769
6770                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6771                     strcat(programStats.movelist, " ");
6772                     strcat(programStats.movelist, p);
6773                 }
6774
6775                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6776                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6777                     DisplayMove(currentMove - 1);
6778                     DisplayAnalysis();
6779                 }
6780                 return;
6781             }
6782         }
6783         else {
6784             buf1[0] = NULLCHAR;
6785
6786             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6787                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
6788             {
6789                 ChessProgramStats cpstats;
6790
6791                 if (plyext != ' ' && plyext != '\t') {
6792                     time *= 100;
6793                 }
6794
6795                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6796                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6797                     curscore = -curscore;
6798                 }
6799
6800                 cpstats.depth = plylev;
6801                 cpstats.nodes = nodes;
6802                 cpstats.time = time;
6803                 cpstats.score = curscore;
6804                 cpstats.got_only_move = 0;
6805                 cpstats.movelist[0] = '\0';
6806
6807                 if (buf1[0] != NULLCHAR) {
6808                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6809                 }
6810
6811                 cpstats.ok_to_send = 0;
6812                 cpstats.line_is_book = 0;
6813                 cpstats.nr_moves = 0;
6814                 cpstats.moves_left = 0;
6815
6816                 SendProgramStatsToFrontend( cps, &cpstats );
6817             }
6818         }
6819     }
6820 }
6821
6822
6823 /* Parse a game score from the character string "game", and
6824    record it as the history of the current game.  The game
6825    score is NOT assumed to start from the standard position. 
6826    The display is not updated in any way.
6827    */
6828 void
6829 ParseGameHistory(game)
6830      char *game;
6831 {
6832     ChessMove moveType;
6833     int fromX, fromY, toX, toY, boardIndex;
6834     char promoChar;
6835     char *p, *q;
6836     char buf[MSG_SIZ];
6837
6838     if (appData.debugMode)
6839       fprintf(debugFP, "Parsing game history: %s\n", game);
6840
6841     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6842     gameInfo.site = StrSave(appData.icsHost);
6843     gameInfo.date = PGNDate();
6844     gameInfo.round = StrSave("-");
6845
6846     /* Parse out names of players */
6847     while (*game == ' ') game++;
6848     p = buf;
6849     while (*game != ' ') *p++ = *game++;
6850     *p = NULLCHAR;
6851     gameInfo.white = StrSave(buf);
6852     while (*game == ' ') game++;
6853     p = buf;
6854     while (*game != ' ' && *game != '\n') *p++ = *game++;
6855     *p = NULLCHAR;
6856     gameInfo.black = StrSave(buf);
6857
6858     /* Parse moves */
6859     boardIndex = blackPlaysFirst ? 1 : 0;
6860     yynewstr(game);
6861     for (;;) {
6862         yyboardindex = boardIndex;
6863         moveType = (ChessMove) yylex();
6864         switch (moveType) {
6865           case IllegalMove:             /* maybe suicide chess, etc. */
6866   if (appData.debugMode) {
6867     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6868     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6869     setbuf(debugFP, NULL);
6870   }
6871           case WhitePromotionChancellor:
6872           case BlackPromotionChancellor:
6873           case WhitePromotionArchbishop:
6874           case BlackPromotionArchbishop:
6875           case WhitePromotionQueen:
6876           case BlackPromotionQueen:
6877           case WhitePromotionRook:
6878           case BlackPromotionRook:
6879           case WhitePromotionBishop:
6880           case BlackPromotionBishop:
6881           case WhitePromotionKnight:
6882           case BlackPromotionKnight:
6883           case WhitePromotionKing:
6884           case BlackPromotionKing:
6885           case NormalMove:
6886           case WhiteCapturesEnPassant:
6887           case BlackCapturesEnPassant:
6888           case WhiteKingSideCastle:
6889           case WhiteQueenSideCastle:
6890           case BlackKingSideCastle:
6891           case BlackQueenSideCastle:
6892           case WhiteKingSideCastleWild:
6893           case WhiteQueenSideCastleWild:
6894           case BlackKingSideCastleWild:
6895           case BlackQueenSideCastleWild:
6896           /* PUSH Fabien */
6897           case WhiteHSideCastleFR:
6898           case WhiteASideCastleFR:
6899           case BlackHSideCastleFR:
6900           case BlackASideCastleFR:
6901           /* POP Fabien */
6902             fromX = currentMoveString[0] - AAA;
6903             fromY = currentMoveString[1] - ONE;
6904             toX = currentMoveString[2] - AAA;
6905             toY = currentMoveString[3] - ONE;
6906             promoChar = currentMoveString[4];
6907             break;
6908           case WhiteDrop:
6909           case BlackDrop:
6910             fromX = moveType == WhiteDrop ?
6911               (int) CharToPiece(ToUpper(currentMoveString[0])) :
6912             (int) CharToPiece(ToLower(currentMoveString[0]));
6913             fromY = DROP_RANK;
6914             toX = currentMoveString[2] - AAA;
6915             toY = currentMoveString[3] - ONE;
6916             promoChar = NULLCHAR;
6917             break;
6918           case AmbiguousMove:
6919             /* bug? */
6920             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6921   if (appData.debugMode) {
6922     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6923     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6924     setbuf(debugFP, NULL);
6925   }
6926             DisplayError(buf, 0);
6927             return;
6928           case ImpossibleMove:
6929             /* bug? */
6930             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6931   if (appData.debugMode) {
6932     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6933     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6934     setbuf(debugFP, NULL);
6935   }
6936             DisplayError(buf, 0);
6937             return;
6938           case (ChessMove) 0:   /* end of file */
6939             if (boardIndex < backwardMostMove) {
6940                 /* Oops, gap.  How did that happen? */
6941                 DisplayError(_("Gap in move list"), 0);
6942                 return;
6943             }
6944             backwardMostMove =  blackPlaysFirst ? 1 : 0;
6945             if (boardIndex > forwardMostMove) {
6946                 forwardMostMove = boardIndex;
6947             }
6948             return;
6949           case ElapsedTime:
6950             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
6951                 strcat(parseList[boardIndex-1], " ");
6952                 strcat(parseList[boardIndex-1], yy_text);
6953             }
6954             continue;
6955           case Comment:
6956           case PGNTag:
6957           case NAG:
6958           default:
6959             /* ignore */
6960             continue;
6961           case WhiteWins:
6962           case BlackWins:
6963           case GameIsDrawn:
6964           case GameUnfinished:
6965             if (gameMode == IcsExamining) {
6966                 if (boardIndex < backwardMostMove) {
6967                     /* Oops, gap.  How did that happen? */
6968                     return;
6969                 }
6970                 backwardMostMove = blackPlaysFirst ? 1 : 0;
6971                 return;
6972             }
6973             gameInfo.result = moveType;
6974             p = strchr(yy_text, '{');
6975             if (p == NULL) p = strchr(yy_text, '(');
6976             if (p == NULL) {
6977                 p = yy_text;
6978                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
6979             } else {
6980                 q = strchr(p, *p == '{' ? '}' : ')');
6981                 if (q != NULL) *q = NULLCHAR;
6982                 p++;
6983             }
6984             gameInfo.resultDetails = StrSave(p);
6985             continue;
6986         }
6987         if (boardIndex >= forwardMostMove &&
6988             !(gameMode == IcsObserving && ics_gamenum == -1)) {
6989             backwardMostMove = blackPlaysFirst ? 1 : 0;
6990             return;
6991         }
6992         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
6993                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
6994                                  parseList[boardIndex]);
6995         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
6996         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
6997         /* currentMoveString is set as a side-effect of yylex */
6998         strcpy(moveList[boardIndex], currentMoveString);
6999         strcat(moveList[boardIndex], "\n");
7000         boardIndex++;
7001         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7002                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7003         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7004                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7005           case MT_NONE:
7006           case MT_STALEMATE:
7007           default:
7008             break;
7009           case MT_CHECK:
7010             if(gameInfo.variant != VariantShogi)
7011                 strcat(parseList[boardIndex - 1], "+");
7012             break;
7013           case MT_CHECKMATE:
7014           case MT_STAINMATE:
7015             strcat(parseList[boardIndex - 1], "#");
7016             break;
7017         }
7018     }
7019 }
7020
7021
7022 /* Apply a move to the given board  */
7023 void
7024 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7025      int fromX, fromY, toX, toY;
7026      int promoChar;
7027      Board board;
7028      char *castling;
7029      char *ep;
7030 {
7031   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7032
7033     /* [HGM] compute & store e.p. status and castling rights for new position */
7034     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7035     { int i;
7036
7037       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7038       oldEP = *ep;
7039       *ep = EP_NONE;
7040
7041       if( board[toY][toX] != EmptySquare ) 
7042            *ep = EP_CAPTURE;  
7043
7044       if( board[fromY][fromX] == WhitePawn ) {
7045            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7046                *ep = EP_PAWN_MOVE;
7047            if( toY-fromY==2) {
7048                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7049                         gameInfo.variant != VariantBerolina || toX < fromX)
7050                       *ep = toX | berolina;
7051                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7052                         gameInfo.variant != VariantBerolina || toX > fromX) 
7053                       *ep = toX;
7054            }
7055       } else 
7056       if( board[fromY][fromX] == BlackPawn ) {
7057            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7058                *ep = EP_PAWN_MOVE; 
7059            if( toY-fromY== -2) {
7060                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7061                         gameInfo.variant != VariantBerolina || toX < fromX)
7062                       *ep = toX | berolina;
7063                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7064                         gameInfo.variant != VariantBerolina || toX > fromX) 
7065                       *ep = toX;
7066            }
7067        }
7068
7069        for(i=0; i<nrCastlingRights; i++) {
7070            if(castling[i] == fromX && castlingRank[i] == fromY ||
7071               castling[i] == toX   && castlingRank[i] == toY   
7072              ) castling[i] = -1; // revoke for moved or captured piece
7073        }
7074
7075     }
7076
7077   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7078   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7079        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7080          
7081   if (fromX == toX && fromY == toY) return;
7082
7083   if (fromY == DROP_RANK) {
7084         /* must be first */
7085         piece = board[toY][toX] = (ChessSquare) fromX;
7086   } else {
7087      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7088      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7089      if(gameInfo.variant == VariantKnightmate)
7090          king += (int) WhiteUnicorn - (int) WhiteKing;
7091
7092     /* Code added by Tord: */
7093     /* FRC castling assumed when king captures friendly rook. */
7094     if (board[fromY][fromX] == WhiteKing &&
7095              board[toY][toX] == WhiteRook) {
7096       board[fromY][fromX] = EmptySquare;
7097       board[toY][toX] = EmptySquare;
7098       if(toX > fromX) {
7099         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7100       } else {
7101         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7102       }
7103     } else if (board[fromY][fromX] == BlackKing &&
7104                board[toY][toX] == BlackRook) {
7105       board[fromY][fromX] = EmptySquare;
7106       board[toY][toX] = EmptySquare;
7107       if(toX > fromX) {
7108         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7109       } else {
7110         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7111       }
7112     /* End of code added by Tord */
7113
7114     } else if (board[fromY][fromX] == king
7115         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7116         && toY == fromY && toX > fromX+1) {
7117         board[fromY][fromX] = EmptySquare;
7118         board[toY][toX] = king;
7119         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7120         board[fromY][BOARD_RGHT-1] = EmptySquare;
7121     } else if (board[fromY][fromX] == king
7122         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7123                && toY == fromY && toX < fromX-1) {
7124         board[fromY][fromX] = EmptySquare;
7125         board[toY][toX] = king;
7126         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7127         board[fromY][BOARD_LEFT] = EmptySquare;
7128     } else if (board[fromY][fromX] == WhitePawn
7129                && toY == BOARD_HEIGHT-1
7130                && gameInfo.variant != VariantXiangqi
7131                ) {
7132         /* white pawn promotion */
7133         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7134         if (board[toY][toX] == EmptySquare) {
7135             board[toY][toX] = WhiteQueen;
7136         }
7137         if(gameInfo.variant==VariantBughouse ||
7138            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7139             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7140         board[fromY][fromX] = EmptySquare;
7141     } else if ((fromY == BOARD_HEIGHT-4)
7142                && (toX != fromX)
7143                && gameInfo.variant != VariantXiangqi
7144                && gameInfo.variant != VariantBerolina
7145                && (board[fromY][fromX] == WhitePawn)
7146                && (board[toY][toX] == EmptySquare)) {
7147         board[fromY][fromX] = EmptySquare;
7148         board[toY][toX] = WhitePawn;
7149         captured = board[toY - 1][toX];
7150         board[toY - 1][toX] = EmptySquare;
7151     } else if ((fromY == BOARD_HEIGHT-4)
7152                && (toX == fromX)
7153                && gameInfo.variant == VariantBerolina
7154                && (board[fromY][fromX] == WhitePawn)
7155                && (board[toY][toX] == EmptySquare)) {
7156         board[fromY][fromX] = EmptySquare;
7157         board[toY][toX] = WhitePawn;
7158         if(oldEP & EP_BEROLIN_A) {
7159                 captured = board[fromY][fromX-1];
7160                 board[fromY][fromX-1] = EmptySquare;
7161         }else{  captured = board[fromY][fromX+1];
7162                 board[fromY][fromX+1] = EmptySquare;
7163         }
7164     } else if (board[fromY][fromX] == king
7165         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7166                && toY == fromY && toX > fromX+1) {
7167         board[fromY][fromX] = EmptySquare;
7168         board[toY][toX] = king;
7169         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7170         board[fromY][BOARD_RGHT-1] = EmptySquare;
7171     } else if (board[fromY][fromX] == king
7172         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7173                && toY == fromY && toX < fromX-1) {
7174         board[fromY][fromX] = EmptySquare;
7175         board[toY][toX] = king;
7176         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7177         board[fromY][BOARD_LEFT] = EmptySquare;
7178     } else if (fromY == 7 && fromX == 3
7179                && board[fromY][fromX] == BlackKing
7180                && toY == 7 && toX == 5) {
7181         board[fromY][fromX] = EmptySquare;
7182         board[toY][toX] = BlackKing;
7183         board[fromY][7] = EmptySquare;
7184         board[toY][4] = BlackRook;
7185     } else if (fromY == 7 && fromX == 3
7186                && board[fromY][fromX] == BlackKing
7187                && toY == 7 && toX == 1) {
7188         board[fromY][fromX] = EmptySquare;
7189         board[toY][toX] = BlackKing;
7190         board[fromY][0] = EmptySquare;
7191         board[toY][2] = BlackRook;
7192     } else if (board[fromY][fromX] == BlackPawn
7193                && toY == 0
7194                && gameInfo.variant != VariantXiangqi
7195                ) {
7196         /* black pawn promotion */
7197         board[0][toX] = CharToPiece(ToLower(promoChar));
7198         if (board[0][toX] == EmptySquare) {
7199             board[0][toX] = BlackQueen;
7200         }
7201         if(gameInfo.variant==VariantBughouse ||
7202            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7203             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7204         board[fromY][fromX] = EmptySquare;
7205     } else if ((fromY == 3)
7206                && (toX != fromX)
7207                && gameInfo.variant != VariantXiangqi
7208                && gameInfo.variant != VariantBerolina
7209                && (board[fromY][fromX] == BlackPawn)
7210                && (board[toY][toX] == EmptySquare)) {
7211         board[fromY][fromX] = EmptySquare;
7212         board[toY][toX] = BlackPawn;
7213         captured = board[toY + 1][toX];
7214         board[toY + 1][toX] = EmptySquare;
7215     } else if ((fromY == 3)
7216                && (toX == fromX)
7217                && gameInfo.variant == VariantBerolina
7218                && (board[fromY][fromX] == BlackPawn)
7219                && (board[toY][toX] == EmptySquare)) {
7220         board[fromY][fromX] = EmptySquare;
7221         board[toY][toX] = BlackPawn;
7222         if(oldEP & EP_BEROLIN_A) {
7223                 captured = board[fromY][fromX-1];
7224                 board[fromY][fromX-1] = EmptySquare;
7225         }else{  captured = board[fromY][fromX+1];
7226                 board[fromY][fromX+1] = EmptySquare;
7227         }
7228     } else {
7229         board[toY][toX] = board[fromY][fromX];
7230         board[fromY][fromX] = EmptySquare;
7231     }
7232
7233     /* [HGM] now we promote for Shogi, if needed */
7234     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7235         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7236   }
7237
7238     if (gameInfo.holdingsWidth != 0) {
7239
7240       /* !!A lot more code needs to be written to support holdings  */
7241       /* [HGM] OK, so I have written it. Holdings are stored in the */
7242       /* penultimate board files, so they are automaticlly stored   */
7243       /* in the game history.                                       */
7244       if (fromY == DROP_RANK) {
7245         /* Delete from holdings, by decreasing count */
7246         /* and erasing image if necessary            */
7247         p = (int) fromX;
7248         if(p < (int) BlackPawn) { /* white drop */
7249              p -= (int)WhitePawn;
7250              if(p >= gameInfo.holdingsSize) p = 0;
7251              if(--board[p][BOARD_WIDTH-2] == 0)
7252                   board[p][BOARD_WIDTH-1] = EmptySquare;
7253         } else {                  /* black drop */
7254              p -= (int)BlackPawn;
7255              if(p >= gameInfo.holdingsSize) p = 0;
7256              if(--board[BOARD_HEIGHT-1-p][1] == 0)
7257                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7258         }
7259       }
7260       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7261           && gameInfo.variant != VariantBughouse        ) {
7262         /* [HGM] holdings: Add to holdings, if holdings exist */
7263         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7264                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7265                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7266         }
7267         p = (int) captured;
7268         if (p >= (int) BlackPawn) {
7269           p -= (int)BlackPawn;
7270           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7271                   /* in Shogi restore piece to its original  first */
7272                   captured = (ChessSquare) (DEMOTED captured);
7273                   p = DEMOTED p;
7274           }
7275           p = PieceToNumber((ChessSquare)p);
7276           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7277           board[p][BOARD_WIDTH-2]++;
7278           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7279         } else {
7280           p -= (int)WhitePawn;
7281           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7282                   captured = (ChessSquare) (DEMOTED captured);
7283                   p = DEMOTED p;
7284           }
7285           p = PieceToNumber((ChessSquare)p);
7286           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7287           board[BOARD_HEIGHT-1-p][1]++;
7288           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7289         }
7290       }
7291
7292     } else if (gameInfo.variant == VariantAtomic) {
7293       if (captured != EmptySquare) {
7294         int y, x;
7295         for (y = toY-1; y <= toY+1; y++) {
7296           for (x = toX-1; x <= toX+1; x++) {
7297             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7298                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7299               board[y][x] = EmptySquare;
7300             }
7301           }
7302         }
7303         board[toY][toX] = EmptySquare;
7304       }
7305     }
7306     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7307         /* [HGM] Shogi promotions */
7308         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7309     }
7310
7311     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7312                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7313         // [HGM] superchess: take promotion piece out of holdings
7314         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7315         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7316             if(!--board[k][BOARD_WIDTH-2])
7317                 board[k][BOARD_WIDTH-1] = EmptySquare;
7318         } else {
7319             if(!--board[BOARD_HEIGHT-1-k][1])
7320                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7321         }
7322     }
7323
7324 }
7325
7326 /* Updates forwardMostMove */
7327 void
7328 MakeMove(fromX, fromY, toX, toY, promoChar)
7329      int fromX, fromY, toX, toY;
7330      int promoChar;
7331 {
7332 //    forwardMostMove++; // [HGM] bare: moved downstream
7333
7334     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7335         int timeLeft; static int lastLoadFlag=0; int king, piece;
7336         piece = boards[forwardMostMove][fromY][fromX];
7337         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7338         if(gameInfo.variant == VariantKnightmate)
7339             king += (int) WhiteUnicorn - (int) WhiteKing;
7340         if(forwardMostMove == 0) {
7341             if(blackPlaysFirst) 
7342                 fprintf(serverMoves, "%s;", second.tidy);
7343             fprintf(serverMoves, "%s;", first.tidy);
7344             if(!blackPlaysFirst) 
7345                 fprintf(serverMoves, "%s;", second.tidy);
7346         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7347         lastLoadFlag = loadFlag;
7348         // print base move
7349         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7350         // print castling suffix
7351         if( toY == fromY && piece == king ) {
7352             if(toX-fromX > 1)
7353                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7354             if(fromX-toX >1)
7355                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7356         }
7357         // e.p. suffix
7358         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7359              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7360              boards[forwardMostMove][toY][toX] == EmptySquare
7361              && fromX != toX )
7362                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7363         // promotion suffix
7364         if(promoChar != NULLCHAR)
7365                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7366         if(!loadFlag) {
7367             fprintf(serverMoves, "/%d/%d",
7368                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7369             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7370             else                      timeLeft = blackTimeRemaining/1000;
7371             fprintf(serverMoves, "/%d", timeLeft);
7372         }
7373         fflush(serverMoves);
7374     }
7375
7376     if (forwardMostMove+1 >= MAX_MOVES) {
7377       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7378                         0, 1);
7379       return;
7380     }
7381     SwitchClocks();
7382     timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
7383     timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
7384     if (commentList[forwardMostMove+1] != NULL) {
7385         free(commentList[forwardMostMove+1]);
7386         commentList[forwardMostMove+1] = NULL;
7387     }
7388     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7389     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7390     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7391                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7392     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7393     gameInfo.result = GameUnfinished;
7394     if (gameInfo.resultDetails != NULL) {
7395         free(gameInfo.resultDetails);
7396         gameInfo.resultDetails = NULL;
7397     }
7398     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7399                               moveList[forwardMostMove - 1]);
7400     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7401                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7402                              fromY, fromX, toY, toX, promoChar,
7403                              parseList[forwardMostMove - 1]);
7404     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7405                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7406                             castlingRights[forwardMostMove]) ) {
7407       case MT_NONE:
7408       case MT_STALEMATE:
7409       default:
7410         break;
7411       case MT_CHECK:
7412         if(gameInfo.variant != VariantShogi)
7413             strcat(parseList[forwardMostMove - 1], "+");
7414         break;
7415       case MT_CHECKMATE:
7416       case MT_STAINMATE:
7417         strcat(parseList[forwardMostMove - 1], "#");
7418         break;
7419     }
7420     if (appData.debugMode) {
7421         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7422     }
7423
7424 }
7425
7426 /* Updates currentMove if not pausing */
7427 void
7428 ShowMove(fromX, fromY, toX, toY)
7429 {
7430     int instant = (gameMode == PlayFromGameFile) ?
7431         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7432     if(appData.noGUI) return;
7433     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7434         if (!instant) {
7435             if (forwardMostMove == currentMove + 1) {
7436                 AnimateMove(boards[forwardMostMove - 1],
7437                             fromX, fromY, toX, toY);
7438             }
7439             if (appData.highlightLastMove) {
7440                 SetHighlights(fromX, fromY, toX, toY);
7441             }
7442         }
7443         currentMove = forwardMostMove;
7444     }
7445
7446     if (instant) return;
7447
7448     DisplayMove(currentMove - 1);
7449     DrawPosition(FALSE, boards[currentMove]);
7450     DisplayBothClocks();
7451     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7452 }
7453
7454 void SendEgtPath(ChessProgramState *cps)
7455 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7456         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7457
7458         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7459
7460         while(*p) {
7461             char c, *q = name+1, *r, *s;
7462
7463             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7464             while(*p && *p != ',') *q++ = *p++;
7465             *q++ = ':'; *q = 0;
7466             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7467                 strcmp(name, ",nalimov:") == 0 ) {
7468                 // take nalimov path from the menu-changeable option first, if it is defined
7469                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7470                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7471             } else
7472             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7473                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7474                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7475                 s = r = StrStr(s, ":") + 1; // beginning of path info
7476                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7477                 c = *r; *r = 0;             // temporarily null-terminate path info
7478                     *--q = 0;               // strip of trailig ':' from name
7479                     sprintf(buf, "egtbpath %s %s\n", name+1, s);
7480                 *r = c;
7481                 SendToProgram(buf,cps);     // send egtbpath command for this format
7482             }
7483             if(*p == ',') p++; // read away comma to position for next format name
7484         }
7485 }
7486
7487 void
7488 InitChessProgram(cps, setup)
7489      ChessProgramState *cps;
7490      int setup; /* [HGM] needed to setup FRC opening position */
7491 {
7492     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7493     if (appData.noChessProgram) return;
7494     hintRequested = FALSE;
7495     bookRequested = FALSE;
7496
7497     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7498     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7499     if(cps->memSize) { /* [HGM] memory */
7500         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7501         SendToProgram(buf, cps);
7502     }
7503     SendEgtPath(cps); /* [HGM] EGT */
7504     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7505         sprintf(buf, "cores %d\n", appData.smpCores);
7506         SendToProgram(buf, cps);
7507     }
7508
7509     SendToProgram(cps->initString, cps);
7510     if (gameInfo.variant != VariantNormal &&
7511         gameInfo.variant != VariantLoadable
7512         /* [HGM] also send variant if board size non-standard */
7513         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7514                                             ) {
7515       char *v = VariantName(gameInfo.variant);
7516       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7517         /* [HGM] in protocol 1 we have to assume all variants valid */
7518         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7519         DisplayFatalError(buf, 0, 1);
7520         return;
7521       }
7522
7523       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7524       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7525       if( gameInfo.variant == VariantXiangqi )
7526            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7527       if( gameInfo.variant == VariantShogi )
7528            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7529       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7530            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7531       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7532                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7533            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7534       if( gameInfo.variant == VariantCourier )
7535            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7536       if( gameInfo.variant == VariantSuper )
7537            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7538       if( gameInfo.variant == VariantGreat )
7539            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7540
7541       if(overruled) {
7542            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7543                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7544            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7545            if(StrStr(cps->variants, b) == NULL) { 
7546                // specific sized variant not known, check if general sizing allowed
7547                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7548                    if(StrStr(cps->variants, "boardsize") == NULL) {
7549                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7550                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7551                        DisplayFatalError(buf, 0, 1);
7552                        return;
7553                    }
7554                    /* [HGM] here we really should compare with the maximum supported board size */
7555                }
7556            }
7557       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7558       sprintf(buf, "variant %s\n", b);
7559       SendToProgram(buf, cps);
7560     }
7561     currentlyInitializedVariant = gameInfo.variant;
7562
7563     /* [HGM] send opening position in FRC to first engine */
7564     if(setup) {
7565           SendToProgram("force\n", cps);
7566           SendBoard(cps, 0);
7567           /* engine is now in force mode! Set flag to wake it up after first move. */
7568           setboardSpoiledMachineBlack = 1;
7569     }
7570
7571     if (cps->sendICS) {
7572       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7573       SendToProgram(buf, cps);
7574     }
7575     cps->maybeThinking = FALSE;
7576     cps->offeredDraw = 0;
7577     if (!appData.icsActive) {
7578         SendTimeControl(cps, movesPerSession, timeControl,
7579                         timeIncrement, appData.searchDepth,
7580                         searchTime);
7581     }
7582     if (appData.showThinking 
7583         // [HGM] thinking: four options require thinking output to be sent
7584         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7585                                 ) {
7586         SendToProgram("post\n", cps);
7587     }
7588     SendToProgram("hard\n", cps);
7589     if (!appData.ponderNextMove) {
7590         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7591            it without being sure what state we are in first.  "hard"
7592            is not a toggle, so that one is OK.
7593          */
7594         SendToProgram("easy\n", cps);
7595     }
7596     if (cps->usePing) {
7597       sprintf(buf, "ping %d\n", ++cps->lastPing);
7598       SendToProgram(buf, cps);
7599     }
7600     cps->initDone = TRUE;
7601 }   
7602
7603
7604 void
7605 StartChessProgram(cps)
7606      ChessProgramState *cps;
7607 {
7608     char buf[MSG_SIZ];
7609     int err;
7610
7611     if (appData.noChessProgram) return;
7612     cps->initDone = FALSE;
7613
7614     if (strcmp(cps->host, "localhost") == 0) {
7615         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7616     } else if (*appData.remoteShell == NULLCHAR) {
7617         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7618     } else {
7619         if (*appData.remoteUser == NULLCHAR) {
7620           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7621                     cps->program);
7622         } else {
7623           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7624                     cps->host, appData.remoteUser, cps->program);
7625         }
7626         err = StartChildProcess(buf, "", &cps->pr);
7627     }
7628     
7629     if (err != 0) {
7630         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7631         DisplayFatalError(buf, err, 1);
7632         cps->pr = NoProc;
7633         cps->isr = NULL;
7634         return;
7635     }
7636     
7637     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7638     if (cps->protocolVersion > 1) {
7639       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7640       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7641       cps->comboCnt = 0;  //                and values of combo boxes
7642       SendToProgram(buf, cps);
7643     } else {
7644       SendToProgram("xboard\n", cps);
7645     }
7646 }
7647
7648
7649 void
7650 TwoMachinesEventIfReady P((void))
7651 {
7652   if (first.lastPing != first.lastPong) {
7653     DisplayMessage("", _("Waiting for first chess program"));
7654     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7655     return;
7656   }
7657   if (second.lastPing != second.lastPong) {
7658     DisplayMessage("", _("Waiting for second chess program"));
7659     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7660     return;
7661   }
7662   ThawUI();
7663   TwoMachinesEvent();
7664 }
7665
7666 void
7667 NextMatchGame P((void))
7668 {
7669     int index; /* [HGM] autoinc: step lod index during match */
7670     Reset(FALSE, TRUE);
7671     if (*appData.loadGameFile != NULLCHAR) {
7672         index = appData.loadGameIndex;
7673         if(index < 0) { // [HGM] autoinc
7674             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7675             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7676         } 
7677         LoadGameFromFile(appData.loadGameFile,
7678                          index,
7679                          appData.loadGameFile, FALSE);
7680     } else if (*appData.loadPositionFile != NULLCHAR) {
7681         index = appData.loadPositionIndex;
7682         if(index < 0) { // [HGM] autoinc
7683             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7684             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7685         } 
7686         LoadPositionFromFile(appData.loadPositionFile,
7687                              index,
7688                              appData.loadPositionFile);
7689     }
7690     TwoMachinesEventIfReady();
7691 }
7692
7693 void UserAdjudicationEvent( int result )
7694 {
7695     ChessMove gameResult = GameIsDrawn;
7696
7697     if( result > 0 ) {
7698         gameResult = WhiteWins;
7699     }
7700     else if( result < 0 ) {
7701         gameResult = BlackWins;
7702     }
7703
7704     if( gameMode == TwoMachinesPlay ) {
7705         GameEnds( gameResult, "User adjudication", GE_XBOARD );
7706     }
7707 }
7708
7709
7710 void
7711 GameEnds(result, resultDetails, whosays)
7712      ChessMove result;
7713      char *resultDetails;
7714      int whosays;
7715 {
7716     GameMode nextGameMode;
7717     int isIcsGame;
7718     char buf[MSG_SIZ];
7719
7720     if(endingGame) return; /* [HGM] crash: forbid recursion */
7721     endingGame = 1;
7722
7723     if (appData.debugMode) {
7724       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7725               result, resultDetails ? resultDetails : "(null)", whosays);
7726     }
7727
7728     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7729         /* If we are playing on ICS, the server decides when the
7730            game is over, but the engine can offer to draw, claim 
7731            a draw, or resign. 
7732          */
7733 #if ZIPPY
7734         if (appData.zippyPlay && first.initDone) {
7735             if (result == GameIsDrawn) {
7736                 /* In case draw still needs to be claimed */
7737                 SendToICS(ics_prefix);
7738                 SendToICS("draw\n");
7739             } else if (StrCaseStr(resultDetails, "resign")) {
7740                 SendToICS(ics_prefix);
7741                 SendToICS("resign\n");
7742             }
7743         }
7744 #endif
7745         endingGame = 0; /* [HGM] crash */
7746         return;
7747     }
7748
7749     /* If we're loading the game from a file, stop */
7750     if (whosays == GE_FILE) {
7751       (void) StopLoadGameTimer();
7752       gameFileFP = NULL;
7753     }
7754
7755     /* Cancel draw offers */
7756     first.offeredDraw = second.offeredDraw = 0;
7757
7758     /* If this is an ICS game, only ICS can really say it's done;
7759        if not, anyone can. */
7760     isIcsGame = (gameMode == IcsPlayingWhite || 
7761                  gameMode == IcsPlayingBlack || 
7762                  gameMode == IcsObserving    || 
7763                  gameMode == IcsExamining);
7764
7765     if (!isIcsGame || whosays == GE_ICS) {
7766         /* OK -- not an ICS game, or ICS said it was done */
7767         StopClocks();
7768         if (!isIcsGame && !appData.noChessProgram) 
7769           SetUserThinkingEnables();
7770     
7771         /* [HGM] if a machine claims the game end we verify this claim */
7772         if(gameMode == TwoMachinesPlay && appData.testClaims) {
7773             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7774                 char claimer;
7775                 ChessMove trueResult = (ChessMove) -1;
7776
7777                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
7778                                             first.twoMachinesColor[0] :
7779                                             second.twoMachinesColor[0] ;
7780
7781                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7782                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7783                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7784                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7785                 } else
7786                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7787                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7788                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7789                 } else
7790                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7791                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7792                 }
7793
7794                 // now verify win claims, but not in drop games, as we don't understand those yet
7795                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7796                                                  || gameInfo.variant == VariantGreat) &&
7797                     (result == WhiteWins && claimer == 'w' ||
7798                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
7799                       if (appData.debugMode) {
7800                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
7801                                 result, epStatus[forwardMostMove], forwardMostMove);
7802                       }
7803                       if(result != trueResult) {
7804                               sprintf(buf, "False win claim: '%s'", resultDetails);
7805                               result = claimer == 'w' ? BlackWins : WhiteWins;
7806                               resultDetails = buf;
7807                       }
7808                 } else
7809                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7810                     && (forwardMostMove <= backwardMostMove ||
7811                         epStatus[forwardMostMove-1] > EP_DRAWS ||
7812                         (claimer=='b')==(forwardMostMove&1))
7813                                                                                   ) {
7814                       /* [HGM] verify: draws that were not flagged are false claims */
7815                       sprintf(buf, "False draw claim: '%s'", resultDetails);
7816                       result = claimer == 'w' ? BlackWins : WhiteWins;
7817                       resultDetails = buf;
7818                 }
7819                 /* (Claiming a loss is accepted no questions asked!) */
7820             }
7821             /* [HGM] bare: don't allow bare King to win */
7822             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7823                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
7824                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7825                && result != GameIsDrawn)
7826             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7827                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7828                         int p = (int)boards[forwardMostMove][i][j] - color;
7829                         if(p >= 0 && p <= (int)WhiteKing) k++;
7830                 }
7831                 if (appData.debugMode) {
7832                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7833                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7834                 }
7835                 if(k <= 1) {
7836                         result = GameIsDrawn;
7837                         sprintf(buf, "%s but bare king", resultDetails);
7838                         resultDetails = buf;
7839                 }
7840             }
7841         }
7842
7843
7844         if(serverMoves != NULL && !loadFlag) { char c = '=';
7845             if(result==WhiteWins) c = '+';
7846             if(result==BlackWins) c = '-';
7847             if(resultDetails != NULL)
7848                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7849         }
7850         if (resultDetails != NULL) {
7851             gameInfo.result = result;
7852             gameInfo.resultDetails = StrSave(resultDetails);
7853
7854             /* display last move only if game was not loaded from file */
7855             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7856                 DisplayMove(currentMove - 1);
7857     
7858             if (forwardMostMove != 0) {
7859                 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
7860                     if (*appData.saveGameFile != NULLCHAR) {
7861                         SaveGameToFile(appData.saveGameFile, TRUE);
7862                     } else if (appData.autoSaveGames) {
7863                         AutoSaveGame();
7864                     }
7865                     if (*appData.savePositionFile != NULLCHAR) {
7866                         SavePositionToFile(appData.savePositionFile);
7867                     }
7868                 }
7869             }
7870
7871             /* Tell program how game ended in case it is learning */
7872             /* [HGM] Moved this to after saving the PGN, just in case */
7873             /* engine died and we got here through time loss. In that */
7874             /* case we will get a fatal error writing the pipe, which */
7875             /* would otherwise lose us the PGN.                       */
7876             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
7877             /* output during GameEnds should never be fatal anymore   */
7878             if (gameMode == MachinePlaysWhite ||
7879                 gameMode == MachinePlaysBlack ||
7880                 gameMode == TwoMachinesPlay ||
7881                 gameMode == IcsPlayingWhite ||
7882                 gameMode == IcsPlayingBlack ||
7883                 gameMode == BeginningOfGame) {
7884                 char buf[MSG_SIZ];
7885                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7886                         resultDetails);
7887                 if (first.pr != NoProc) {
7888                     SendToProgram(buf, &first);
7889                 }
7890                 if (second.pr != NoProc &&
7891                     gameMode == TwoMachinesPlay) {
7892                     SendToProgram(buf, &second);
7893                 }
7894             }
7895         }
7896
7897         if (appData.icsActive) {
7898             if (appData.quietPlay &&
7899                 (gameMode == IcsPlayingWhite ||
7900                  gameMode == IcsPlayingBlack)) {
7901                 SendToICS(ics_prefix);
7902                 SendToICS("set shout 1\n");
7903             }
7904             nextGameMode = IcsIdle;
7905             ics_user_moved = FALSE;
7906             /* clean up premove.  It's ugly when the game has ended and the
7907              * premove highlights are still on the board.
7908              */
7909             if (gotPremove) {
7910               gotPremove = FALSE;
7911               ClearPremoveHighlights();
7912               DrawPosition(FALSE, boards[currentMove]);
7913             }
7914             if (whosays == GE_ICS) {
7915                 switch (result) {
7916                 case WhiteWins:
7917                     if (gameMode == IcsPlayingWhite)
7918                         PlayIcsWinSound();
7919                     else if(gameMode == IcsPlayingBlack)
7920                         PlayIcsLossSound();
7921                     break;
7922                 case BlackWins:
7923                     if (gameMode == IcsPlayingBlack)
7924                         PlayIcsWinSound();
7925                     else if(gameMode == IcsPlayingWhite)
7926                         PlayIcsLossSound();
7927                     break;
7928                 case GameIsDrawn:
7929                     PlayIcsDrawSound();
7930                     break;
7931                 default:
7932                     PlayIcsUnfinishedSound();
7933                 }
7934             }
7935         } else if (gameMode == EditGame ||
7936                    gameMode == PlayFromGameFile || 
7937                    gameMode == AnalyzeMode || 
7938                    gameMode == AnalyzeFile) {
7939             nextGameMode = gameMode;
7940         } else {
7941             nextGameMode = EndOfGame;
7942         }
7943         pausing = FALSE;
7944         ModeHighlight();
7945     } else {
7946         nextGameMode = gameMode;
7947     }
7948
7949     if (appData.noChessProgram) {
7950         gameMode = nextGameMode;
7951         ModeHighlight();
7952         endingGame = 0; /* [HGM] crash */
7953         return;
7954     }
7955
7956     if (first.reuse) {
7957         /* Put first chess program into idle state */
7958         if (first.pr != NoProc &&
7959             (gameMode == MachinePlaysWhite ||
7960              gameMode == MachinePlaysBlack ||
7961              gameMode == TwoMachinesPlay ||
7962              gameMode == IcsPlayingWhite ||
7963              gameMode == IcsPlayingBlack ||
7964              gameMode == BeginningOfGame)) {
7965             SendToProgram("force\n", &first);
7966             if (first.usePing) {
7967               char buf[MSG_SIZ];
7968               sprintf(buf, "ping %d\n", ++first.lastPing);
7969               SendToProgram(buf, &first);
7970             }
7971         }
7972     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
7973         /* Kill off first chess program */
7974         if (first.isr != NULL)
7975           RemoveInputSource(first.isr);
7976         first.isr = NULL;
7977     
7978         if (first.pr != NoProc) {
7979             ExitAnalyzeMode();
7980             DoSleep( appData.delayBeforeQuit );
7981             SendToProgram("quit\n", &first);
7982             DoSleep( appData.delayAfterQuit );
7983             DestroyChildProcess(first.pr, first.useSigterm);
7984         }
7985         first.pr = NoProc;
7986     }
7987     if (second.reuse) {
7988         /* Put second chess program into idle state */
7989         if (second.pr != NoProc &&
7990             gameMode == TwoMachinesPlay) {
7991             SendToProgram("force\n", &second);
7992             if (second.usePing) {
7993               char buf[MSG_SIZ];
7994               sprintf(buf, "ping %d\n", ++second.lastPing);
7995               SendToProgram(buf, &second);
7996             }
7997         }
7998     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
7999         /* Kill off second chess program */
8000         if (second.isr != NULL)
8001           RemoveInputSource(second.isr);
8002         second.isr = NULL;
8003     
8004         if (second.pr != NoProc) {
8005             DoSleep( appData.delayBeforeQuit );
8006             SendToProgram("quit\n", &second);
8007             DoSleep( appData.delayAfterQuit );
8008             DestroyChildProcess(second.pr, second.useSigterm);
8009         }
8010         second.pr = NoProc;
8011     }
8012
8013     if (matchMode && gameMode == TwoMachinesPlay) {
8014         switch (result) {
8015         case WhiteWins:
8016           if (first.twoMachinesColor[0] == 'w') {
8017             first.matchWins++;
8018           } else {
8019             second.matchWins++;
8020           }
8021           break;
8022         case BlackWins:
8023           if (first.twoMachinesColor[0] == 'b') {
8024             first.matchWins++;
8025           } else {
8026             second.matchWins++;
8027           }
8028           break;
8029         default:
8030           break;
8031         }
8032         if (matchGame < appData.matchGames) {
8033             char *tmp;
8034             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8035                 tmp = first.twoMachinesColor;
8036                 first.twoMachinesColor = second.twoMachinesColor;
8037                 second.twoMachinesColor = tmp;
8038             }
8039             gameMode = nextGameMode;
8040             matchGame++;
8041             if(appData.matchPause>10000 || appData.matchPause<10)
8042                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8043             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8044             endingGame = 0; /* [HGM] crash */
8045             return;
8046         } else {
8047             char buf[MSG_SIZ];
8048             gameMode = nextGameMode;
8049             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8050                     first.tidy, second.tidy,
8051                     first.matchWins, second.matchWins,
8052                     appData.matchGames - (first.matchWins + second.matchWins));
8053             DisplayFatalError(buf, 0, 0);
8054         }
8055     }
8056     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8057         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8058       ExitAnalyzeMode();
8059     gameMode = nextGameMode;
8060     ModeHighlight();
8061     endingGame = 0;  /* [HGM] crash */
8062 }
8063
8064 /* Assumes program was just initialized (initString sent).
8065    Leaves program in force mode. */
8066 void
8067 FeedMovesToProgram(cps, upto) 
8068      ChessProgramState *cps;
8069      int upto;
8070 {
8071     int i;
8072     
8073     if (appData.debugMode)
8074       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8075               startedFromSetupPosition ? "position and " : "",
8076               backwardMostMove, upto, cps->which);
8077     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8078         // [HGM] variantswitch: make engine aware of new variant
8079         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8080                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8081         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8082         SendToProgram(buf, cps);
8083         currentlyInitializedVariant = gameInfo.variant;
8084     }
8085     SendToProgram("force\n", cps);
8086     if (startedFromSetupPosition) {
8087         SendBoard(cps, backwardMostMove);
8088     if (appData.debugMode) {
8089         fprintf(debugFP, "feedMoves\n");
8090     }
8091     }
8092     for (i = backwardMostMove; i < upto; i++) {
8093         SendMoveToProgram(i, cps);
8094     }
8095 }
8096
8097
8098 void
8099 ResurrectChessProgram()
8100 {
8101      /* The chess program may have exited.
8102         If so, restart it and feed it all the moves made so far. */
8103
8104     if (appData.noChessProgram || first.pr != NoProc) return;
8105     
8106     StartChessProgram(&first);
8107     InitChessProgram(&first, FALSE);
8108     FeedMovesToProgram(&first, currentMove);
8109
8110     if (!first.sendTime) {
8111         /* can't tell gnuchess what its clock should read,
8112            so we bow to its notion. */
8113         ResetClocks();
8114         timeRemaining[0][currentMove] = whiteTimeRemaining;
8115         timeRemaining[1][currentMove] = blackTimeRemaining;
8116     }
8117
8118     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8119                 appData.icsEngineAnalyze) && first.analysisSupport) {
8120       SendToProgram("analyze\n", &first);
8121       first.analyzing = TRUE;
8122     }
8123 }
8124
8125 /*
8126  * Button procedures
8127  */
8128 void
8129 Reset(redraw, init)
8130      int redraw, init;
8131 {
8132     int i;
8133
8134     if (appData.debugMode) {
8135         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8136                 redraw, init, gameMode);
8137     }
8138     pausing = pauseExamInvalid = FALSE;
8139     startedFromSetupPosition = blackPlaysFirst = FALSE;
8140     firstMove = TRUE;
8141     whiteFlag = blackFlag = FALSE;
8142     userOfferedDraw = FALSE;
8143     hintRequested = bookRequested = FALSE;
8144     first.maybeThinking = FALSE;
8145     second.maybeThinking = FALSE;
8146     first.bookSuspend = FALSE; // [HGM] book
8147     second.bookSuspend = FALSE;
8148     thinkOutput[0] = NULLCHAR;
8149     lastHint[0] = NULLCHAR;
8150     ClearGameInfo(&gameInfo);
8151     gameInfo.variant = StringToVariant(appData.variant);
8152     ics_user_moved = ics_clock_paused = FALSE;
8153     ics_getting_history = H_FALSE;
8154     ics_gamenum = -1;
8155     white_holding[0] = black_holding[0] = NULLCHAR;
8156     ClearProgramStats();
8157     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8158     
8159     ResetFrontEnd();
8160     ClearHighlights();
8161     flipView = appData.flipView;
8162     ClearPremoveHighlights();
8163     gotPremove = FALSE;
8164     alarmSounded = FALSE;
8165
8166     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8167     if(appData.serverMovesName != NULL) {
8168         /* [HGM] prepare to make moves file for broadcasting */
8169         clock_t t = clock();
8170         if(serverMoves != NULL) fclose(serverMoves);
8171         serverMoves = fopen(appData.serverMovesName, "r");
8172         if(serverMoves != NULL) {
8173             fclose(serverMoves);
8174             /* delay 15 sec before overwriting, so all clients can see end */
8175             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8176         }
8177         serverMoves = fopen(appData.serverMovesName, "w");
8178     }
8179
8180     ExitAnalyzeMode();
8181     gameMode = BeginningOfGame;
8182     ModeHighlight();
8183     if(appData.icsActive) gameInfo.variant = VariantNormal;
8184     InitPosition(redraw);
8185     for (i = 0; i < MAX_MOVES; i++) {
8186         if (commentList[i] != NULL) {
8187             free(commentList[i]);
8188             commentList[i] = NULL;
8189         }
8190     }
8191     ResetClocks();
8192     timeRemaining[0][0] = whiteTimeRemaining;
8193     timeRemaining[1][0] = blackTimeRemaining;
8194     if (first.pr == NULL) {
8195         StartChessProgram(&first);
8196     }
8197     if (init) {
8198             InitChessProgram(&first, startedFromSetupPosition);
8199     }
8200     DisplayTitle("");
8201     DisplayMessage("", "");
8202     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8203 }
8204
8205 void
8206 AutoPlayGameLoop()
8207 {
8208     for (;;) {
8209         if (!AutoPlayOneMove())
8210           return;
8211         if (matchMode || appData.timeDelay == 0)
8212           continue;
8213         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8214           return;
8215         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8216         break;
8217     }
8218 }
8219
8220
8221 int
8222 AutoPlayOneMove()
8223 {
8224     int fromX, fromY, toX, toY;
8225
8226     if (appData.debugMode) {
8227       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8228     }
8229
8230     if (gameMode != PlayFromGameFile)
8231       return FALSE;
8232
8233     if (currentMove >= forwardMostMove) {
8234       gameMode = EditGame;
8235       ModeHighlight();
8236
8237       /* [AS] Clear current move marker at the end of a game */
8238       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8239
8240       return FALSE;
8241     }
8242     
8243     toX = moveList[currentMove][2] - AAA;
8244     toY = moveList[currentMove][3] - ONE;
8245
8246     if (moveList[currentMove][1] == '@') {
8247         if (appData.highlightLastMove) {
8248             SetHighlights(-1, -1, toX, toY);
8249         }
8250     } else {
8251         fromX = moveList[currentMove][0] - AAA;
8252         fromY = moveList[currentMove][1] - ONE;
8253
8254         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8255
8256         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8257
8258         if (appData.highlightLastMove) {
8259             SetHighlights(fromX, fromY, toX, toY);
8260         }
8261     }
8262     DisplayMove(currentMove);
8263     SendMoveToProgram(currentMove++, &first);
8264     DisplayBothClocks();
8265     DrawPosition(FALSE, boards[currentMove]);
8266     // [HGM] PV info: always display, routine tests if empty
8267     DisplayComment(currentMove - 1, commentList[currentMove]);
8268     return TRUE;
8269 }
8270
8271
8272 int
8273 LoadGameOneMove(readAhead)
8274      ChessMove readAhead;
8275 {
8276     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8277     char promoChar = NULLCHAR;
8278     ChessMove moveType;
8279     char move[MSG_SIZ];
8280     char *p, *q;
8281     
8282     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8283         gameMode != AnalyzeMode && gameMode != Training) {
8284         gameFileFP = NULL;
8285         return FALSE;
8286     }
8287     
8288     yyboardindex = forwardMostMove;
8289     if (readAhead != (ChessMove)0) {
8290       moveType = readAhead;
8291     } else {
8292       if (gameFileFP == NULL)
8293           return FALSE;
8294       moveType = (ChessMove) yylex();
8295     }
8296     
8297     done = FALSE;
8298     switch (moveType) {
8299       case Comment:
8300         if (appData.debugMode) 
8301           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8302         p = yy_text;
8303         if (*p == '{' || *p == '[' || *p == '(') {
8304             p[strlen(p) - 1] = NULLCHAR;
8305             p++;
8306         }
8307
8308         /* append the comment but don't display it */
8309         while (*p == '\n') p++;
8310         AppendComment(currentMove, p);
8311         return TRUE;
8312
8313       case WhiteCapturesEnPassant:
8314       case BlackCapturesEnPassant:
8315       case WhitePromotionChancellor:
8316       case BlackPromotionChancellor:
8317       case WhitePromotionArchbishop:
8318       case BlackPromotionArchbishop:
8319       case WhitePromotionCentaur:
8320       case BlackPromotionCentaur:
8321       case WhitePromotionQueen:
8322       case BlackPromotionQueen:
8323       case WhitePromotionRook:
8324       case BlackPromotionRook:
8325       case WhitePromotionBishop:
8326       case BlackPromotionBishop:
8327       case WhitePromotionKnight:
8328       case BlackPromotionKnight:
8329       case WhitePromotionKing:
8330       case BlackPromotionKing:
8331       case NormalMove:
8332       case WhiteKingSideCastle:
8333       case WhiteQueenSideCastle:
8334       case BlackKingSideCastle:
8335       case BlackQueenSideCastle:
8336       case WhiteKingSideCastleWild:
8337       case WhiteQueenSideCastleWild:
8338       case BlackKingSideCastleWild:
8339       case BlackQueenSideCastleWild:
8340       /* PUSH Fabien */
8341       case WhiteHSideCastleFR:
8342       case WhiteASideCastleFR:
8343       case BlackHSideCastleFR:
8344       case BlackASideCastleFR:
8345       /* POP Fabien */
8346         if (appData.debugMode)
8347           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8348         fromX = currentMoveString[0] - AAA;
8349         fromY = currentMoveString[1] - ONE;
8350         toX = currentMoveString[2] - AAA;
8351         toY = currentMoveString[3] - ONE;
8352         promoChar = currentMoveString[4];
8353         break;
8354
8355       case WhiteDrop:
8356       case BlackDrop:
8357         if (appData.debugMode)
8358           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8359         fromX = moveType == WhiteDrop ?
8360           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8361         (int) CharToPiece(ToLower(currentMoveString[0]));
8362         fromY = DROP_RANK;
8363         toX = currentMoveString[2] - AAA;
8364         toY = currentMoveString[3] - ONE;
8365         break;
8366
8367       case WhiteWins:
8368       case BlackWins:
8369       case GameIsDrawn:
8370       case GameUnfinished:
8371         if (appData.debugMode)
8372           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8373         p = strchr(yy_text, '{');
8374         if (p == NULL) p = strchr(yy_text, '(');
8375         if (p == NULL) {
8376             p = yy_text;
8377             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8378         } else {
8379             q = strchr(p, *p == '{' ? '}' : ')');
8380             if (q != NULL) *q = NULLCHAR;
8381             p++;
8382         }
8383         GameEnds(moveType, p, GE_FILE);
8384         done = TRUE;
8385         if (cmailMsgLoaded) {
8386             ClearHighlights();
8387             flipView = WhiteOnMove(currentMove);
8388             if (moveType == GameUnfinished) flipView = !flipView;
8389             if (appData.debugMode)
8390               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8391         }
8392         break;
8393
8394       case (ChessMove) 0:       /* end of file */
8395         if (appData.debugMode)
8396           fprintf(debugFP, "Parser hit end of file\n");
8397         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8398                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8399           case MT_NONE:
8400           case MT_CHECK:
8401             break;
8402           case MT_CHECKMATE:
8403           case MT_STAINMATE:
8404             if (WhiteOnMove(currentMove)) {
8405                 GameEnds(BlackWins, "Black mates", GE_FILE);
8406             } else {
8407                 GameEnds(WhiteWins, "White mates", GE_FILE);
8408             }
8409             break;
8410           case MT_STALEMATE:
8411             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8412             break;
8413         }
8414         done = TRUE;
8415         break;
8416
8417       case MoveNumberOne:
8418         if (lastLoadGameStart == GNUChessGame) {
8419             /* GNUChessGames have numbers, but they aren't move numbers */
8420             if (appData.debugMode)
8421               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8422                       yy_text, (int) moveType);
8423             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8424         }
8425         /* else fall thru */
8426
8427       case XBoardGame:
8428       case GNUChessGame:
8429       case PGNTag:
8430         /* Reached start of next game in file */
8431         if (appData.debugMode)
8432           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8433         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8434                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8435           case MT_NONE:
8436           case MT_CHECK:
8437             break;
8438           case MT_CHECKMATE:
8439           case MT_STAINMATE:
8440             if (WhiteOnMove(currentMove)) {
8441                 GameEnds(BlackWins, "Black mates", GE_FILE);
8442             } else {
8443                 GameEnds(WhiteWins, "White mates", GE_FILE);
8444             }
8445             break;
8446           case MT_STALEMATE:
8447             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8448             break;
8449         }
8450         done = TRUE;
8451         break;
8452
8453       case PositionDiagram:     /* should not happen; ignore */
8454       case ElapsedTime:         /* ignore */
8455       case NAG:                 /* ignore */
8456         if (appData.debugMode)
8457           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8458                   yy_text, (int) moveType);
8459         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8460
8461       case IllegalMove:
8462         if (appData.testLegality) {
8463             if (appData.debugMode)
8464               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8465             sprintf(move, _("Illegal move: %d.%s%s"),
8466                     (forwardMostMove / 2) + 1,
8467                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8468             DisplayError(move, 0);
8469             done = TRUE;
8470         } else {
8471             if (appData.debugMode)
8472               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8473                       yy_text, currentMoveString);
8474             fromX = currentMoveString[0] - AAA;
8475             fromY = currentMoveString[1] - ONE;
8476             toX = currentMoveString[2] - AAA;
8477             toY = currentMoveString[3] - ONE;
8478             promoChar = currentMoveString[4];
8479         }
8480         break;
8481
8482       case AmbiguousMove:
8483         if (appData.debugMode)
8484           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8485         sprintf(move, _("Ambiguous move: %d.%s%s"),
8486                 (forwardMostMove / 2) + 1,
8487                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8488         DisplayError(move, 0);
8489         done = TRUE;
8490         break;
8491
8492       default:
8493       case ImpossibleMove:
8494         if (appData.debugMode)
8495           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8496         sprintf(move, _("Illegal move: %d.%s%s"),
8497                 (forwardMostMove / 2) + 1,
8498                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8499         DisplayError(move, 0);
8500         done = TRUE;
8501         break;
8502     }
8503
8504     if (done) {
8505         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8506             DrawPosition(FALSE, boards[currentMove]);
8507             DisplayBothClocks();
8508             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8509               DisplayComment(currentMove - 1, commentList[currentMove]);
8510         }
8511         (void) StopLoadGameTimer();
8512         gameFileFP = NULL;
8513         cmailOldMove = forwardMostMove;
8514         return FALSE;
8515     } else {
8516         /* currentMoveString is set as a side-effect of yylex */
8517         strcat(currentMoveString, "\n");
8518         strcpy(moveList[forwardMostMove], currentMoveString);
8519         
8520         thinkOutput[0] = NULLCHAR;
8521         MakeMove(fromX, fromY, toX, toY, promoChar);
8522         currentMove = forwardMostMove;
8523         return TRUE;
8524     }
8525 }
8526
8527 /* Load the nth game from the given file */
8528 int
8529 LoadGameFromFile(filename, n, title, useList)
8530      char *filename;
8531      int n;
8532      char *title;
8533      /*Boolean*/ int useList;
8534 {
8535     FILE *f;
8536     char buf[MSG_SIZ];
8537
8538     if (strcmp(filename, "-") == 0) {
8539         f = stdin;
8540         title = "stdin";
8541     } else {
8542         f = fopen(filename, "rb");
8543         if (f == NULL) {
8544           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8545             DisplayError(buf, errno);
8546             return FALSE;
8547         }
8548     }
8549     if (fseek(f, 0, 0) == -1) {
8550         /* f is not seekable; probably a pipe */
8551         useList = FALSE;
8552     }
8553     if (useList && n == 0) {
8554         int error = GameListBuild(f);
8555         if (error) {
8556             DisplayError(_("Cannot build game list"), error);
8557         } else if (!ListEmpty(&gameList) &&
8558                    ((ListGame *) gameList.tailPred)->number > 1) {
8559             GameListPopUp(f, title);
8560             return TRUE;
8561         }
8562         GameListDestroy();
8563         n = 1;
8564     }
8565     if (n == 0) n = 1;
8566     return LoadGame(f, n, title, FALSE);
8567 }
8568
8569
8570 void
8571 MakeRegisteredMove()
8572 {
8573     int fromX, fromY, toX, toY;
8574     char promoChar;
8575     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8576         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8577           case CMAIL_MOVE:
8578           case CMAIL_DRAW:
8579             if (appData.debugMode)
8580               fprintf(debugFP, "Restoring %s for game %d\n",
8581                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8582     
8583             thinkOutput[0] = NULLCHAR;
8584             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8585             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8586             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8587             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8588             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8589             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8590             MakeMove(fromX, fromY, toX, toY, promoChar);
8591             ShowMove(fromX, fromY, toX, toY);
8592               
8593             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8594                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8595               case MT_NONE:
8596               case MT_CHECK:
8597                 break;
8598                 
8599               case MT_CHECKMATE:
8600               case MT_STAINMATE:
8601                 if (WhiteOnMove(currentMove)) {
8602                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8603                 } else {
8604                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8605                 }
8606                 break;
8607                 
8608               case MT_STALEMATE:
8609                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8610                 break;
8611             }
8612
8613             break;
8614             
8615           case CMAIL_RESIGN:
8616             if (WhiteOnMove(currentMove)) {
8617                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8618             } else {
8619                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8620             }
8621             break;
8622             
8623           case CMAIL_ACCEPT:
8624             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8625             break;
8626               
8627           default:
8628             break;
8629         }
8630     }
8631
8632     return;
8633 }
8634
8635 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8636 int
8637 CmailLoadGame(f, gameNumber, title, useList)
8638      FILE *f;
8639      int gameNumber;
8640      char *title;
8641      int useList;
8642 {
8643     int retVal;
8644
8645     if (gameNumber > nCmailGames) {
8646         DisplayError(_("No more games in this message"), 0);
8647         return FALSE;
8648     }
8649     if (f == lastLoadGameFP) {
8650         int offset = gameNumber - lastLoadGameNumber;
8651         if (offset == 0) {
8652             cmailMsg[0] = NULLCHAR;
8653             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8654                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8655                 nCmailMovesRegistered--;
8656             }
8657             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8658             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8659                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8660             }
8661         } else {
8662             if (! RegisterMove()) return FALSE;
8663         }
8664     }
8665
8666     retVal = LoadGame(f, gameNumber, title, useList);
8667
8668     /* Make move registered during previous look at this game, if any */
8669     MakeRegisteredMove();
8670
8671     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8672         commentList[currentMove]
8673           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8674         DisplayComment(currentMove - 1, commentList[currentMove]);
8675     }
8676
8677     return retVal;
8678 }
8679
8680 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8681 int
8682 ReloadGame(offset)
8683      int offset;
8684 {
8685     int gameNumber = lastLoadGameNumber + offset;
8686     if (lastLoadGameFP == NULL) {
8687         DisplayError(_("No game has been loaded yet"), 0);
8688         return FALSE;
8689     }
8690     if (gameNumber <= 0) {
8691         DisplayError(_("Can't back up any further"), 0);
8692         return FALSE;
8693     }
8694     if (cmailMsgLoaded) {
8695         return CmailLoadGame(lastLoadGameFP, gameNumber,
8696                              lastLoadGameTitle, lastLoadGameUseList);
8697     } else {
8698         return LoadGame(lastLoadGameFP, gameNumber,
8699                         lastLoadGameTitle, lastLoadGameUseList);
8700     }
8701 }
8702
8703
8704
8705 /* Load the nth game from open file f */
8706 int
8707 LoadGame(f, gameNumber, title, useList)
8708      FILE *f;
8709      int gameNumber;
8710      char *title;
8711      int useList;
8712 {
8713     ChessMove cm;
8714     char buf[MSG_SIZ];
8715     int gn = gameNumber;
8716     ListGame *lg = NULL;
8717     int numPGNTags = 0;
8718     int err;
8719     GameMode oldGameMode;
8720     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8721
8722     if (appData.debugMode) 
8723         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8724
8725     if (gameMode == Training )
8726         SetTrainingModeOff();
8727
8728     oldGameMode = gameMode;
8729     if (gameMode != BeginningOfGame) {
8730       Reset(FALSE, TRUE);
8731     }
8732
8733     gameFileFP = f;
8734     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8735         fclose(lastLoadGameFP);
8736     }
8737
8738     if (useList) {
8739         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8740         
8741         if (lg) {
8742             fseek(f, lg->offset, 0);
8743             GameListHighlight(gameNumber);
8744             gn = 1;
8745         }
8746         else {
8747             DisplayError(_("Game number out of range"), 0);
8748             return FALSE;
8749         }
8750     } else {
8751         GameListDestroy();
8752         if (fseek(f, 0, 0) == -1) {
8753             if (f == lastLoadGameFP ?
8754                 gameNumber == lastLoadGameNumber + 1 :
8755                 gameNumber == 1) {
8756                 gn = 1;
8757             } else {
8758                 DisplayError(_("Can't seek on game file"), 0);
8759                 return FALSE;
8760             }
8761         }
8762     }
8763     lastLoadGameFP = f;
8764     lastLoadGameNumber = gameNumber;
8765     strcpy(lastLoadGameTitle, title);
8766     lastLoadGameUseList = useList;
8767
8768     yynewfile(f);
8769
8770     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8771       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8772                 lg->gameInfo.black);
8773             DisplayTitle(buf);
8774     } else if (*title != NULLCHAR) {
8775         if (gameNumber > 1) {
8776             sprintf(buf, "%s %d", title, gameNumber);
8777             DisplayTitle(buf);
8778         } else {
8779             DisplayTitle(title);
8780         }
8781     }
8782
8783     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8784         gameMode = PlayFromGameFile;
8785         ModeHighlight();
8786     }
8787
8788     currentMove = forwardMostMove = backwardMostMove = 0;
8789     CopyBoard(boards[0], initialPosition);
8790     StopClocks();
8791
8792     /*
8793      * Skip the first gn-1 games in the file.
8794      * Also skip over anything that precedes an identifiable 
8795      * start of game marker, to avoid being confused by 
8796      * garbage at the start of the file.  Currently 
8797      * recognized start of game markers are the move number "1",
8798      * the pattern "gnuchess .* game", the pattern
8799      * "^[#;%] [^ ]* game file", and a PGN tag block.  
8800      * A game that starts with one of the latter two patterns
8801      * will also have a move number 1, possibly
8802      * following a position diagram.
8803      * 5-4-02: Let's try being more lenient and allowing a game to
8804      * start with an unnumbered move.  Does that break anything?
8805      */
8806     cm = lastLoadGameStart = (ChessMove) 0;
8807     while (gn > 0) {
8808         yyboardindex = forwardMostMove;
8809         cm = (ChessMove) yylex();
8810         switch (cm) {
8811           case (ChessMove) 0:
8812             if (cmailMsgLoaded) {
8813                 nCmailGames = CMAIL_MAX_GAMES - gn;
8814             } else {
8815                 Reset(TRUE, TRUE);
8816                 DisplayError(_("Game not found in file"), 0);
8817             }
8818             return FALSE;
8819
8820           case GNUChessGame:
8821           case XBoardGame:
8822             gn--;
8823             lastLoadGameStart = cm;
8824             break;
8825             
8826           case MoveNumberOne:
8827             switch (lastLoadGameStart) {
8828               case GNUChessGame:
8829               case XBoardGame:
8830               case PGNTag:
8831                 break;
8832               case MoveNumberOne:
8833               case (ChessMove) 0:
8834                 gn--;           /* count this game */
8835                 lastLoadGameStart = cm;
8836                 break;
8837               default:
8838                 /* impossible */
8839                 break;
8840             }
8841             break;
8842
8843           case PGNTag:
8844             switch (lastLoadGameStart) {
8845               case GNUChessGame:
8846               case PGNTag:
8847               case MoveNumberOne:
8848               case (ChessMove) 0:
8849                 gn--;           /* count this game */
8850                 lastLoadGameStart = cm;
8851                 break;
8852               case XBoardGame:
8853                 lastLoadGameStart = cm; /* game counted already */
8854                 break;
8855               default:
8856                 /* impossible */
8857                 break;
8858             }
8859             if (gn > 0) {
8860                 do {
8861                     yyboardindex = forwardMostMove;
8862                     cm = (ChessMove) yylex();
8863                 } while (cm == PGNTag || cm == Comment);
8864             }
8865             break;
8866
8867           case WhiteWins:
8868           case BlackWins:
8869           case GameIsDrawn:
8870             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8871                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
8872                     != CMAIL_OLD_RESULT) {
8873                     nCmailResults ++ ;
8874                     cmailResult[  CMAIL_MAX_GAMES
8875                                 - gn - 1] = CMAIL_OLD_RESULT;
8876                 }
8877             }
8878             break;
8879
8880           case NormalMove:
8881             /* Only a NormalMove can be at the start of a game
8882              * without a position diagram. */
8883             if (lastLoadGameStart == (ChessMove) 0) {
8884               gn--;
8885               lastLoadGameStart = MoveNumberOne;
8886             }
8887             break;
8888
8889           default:
8890             break;
8891         }
8892     }
8893     
8894     if (appData.debugMode)
8895       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8896
8897     if (cm == XBoardGame) {
8898         /* Skip any header junk before position diagram and/or move 1 */
8899         for (;;) {
8900             yyboardindex = forwardMostMove;
8901             cm = (ChessMove) yylex();
8902
8903             if (cm == (ChessMove) 0 ||
8904                 cm == GNUChessGame || cm == XBoardGame) {
8905                 /* Empty game; pretend end-of-file and handle later */
8906                 cm = (ChessMove) 0;
8907                 break;
8908             }
8909
8910             if (cm == MoveNumberOne || cm == PositionDiagram ||
8911                 cm == PGNTag || cm == Comment)
8912               break;
8913         }
8914     } else if (cm == GNUChessGame) {
8915         if (gameInfo.event != NULL) {
8916             free(gameInfo.event);
8917         }
8918         gameInfo.event = StrSave(yy_text);
8919     }   
8920
8921     startedFromSetupPosition = FALSE;
8922     while (cm == PGNTag) {
8923         if (appData.debugMode) 
8924           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
8925         err = ParsePGNTag(yy_text, &gameInfo);
8926         if (!err) numPGNTags++;
8927
8928         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
8929         if(gameInfo.variant != oldVariant) {
8930             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
8931             InitPosition(TRUE);
8932             oldVariant = gameInfo.variant;
8933             if (appData.debugMode) 
8934               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
8935         }
8936
8937
8938         if (gameInfo.fen != NULL) {
8939           Board initial_position;
8940           startedFromSetupPosition = TRUE;
8941           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
8942             Reset(TRUE, TRUE);
8943             DisplayError(_("Bad FEN position in file"), 0);
8944             return FALSE;
8945           }
8946           CopyBoard(boards[0], initial_position);
8947           if (blackPlaysFirst) {
8948             currentMove = forwardMostMove = backwardMostMove = 1;
8949             CopyBoard(boards[1], initial_position);
8950             strcpy(moveList[0], "");
8951             strcpy(parseList[0], "");
8952             timeRemaining[0][1] = whiteTimeRemaining;
8953             timeRemaining[1][1] = blackTimeRemaining;
8954             if (commentList[0] != NULL) {
8955               commentList[1] = commentList[0];
8956               commentList[0] = NULL;
8957             }
8958           } else {
8959             currentMove = forwardMostMove = backwardMostMove = 0;
8960           }
8961           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
8962           {   int i;
8963               initialRulePlies = FENrulePlies;
8964               epStatus[forwardMostMove] = FENepStatus;
8965               for( i=0; i< nrCastlingRights; i++ )
8966                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
8967           }
8968           yyboardindex = forwardMostMove;
8969           free(gameInfo.fen);
8970           gameInfo.fen = NULL;
8971         }
8972
8973         yyboardindex = forwardMostMove;
8974         cm = (ChessMove) yylex();
8975
8976         /* Handle comments interspersed among the tags */
8977         while (cm == Comment) {
8978             char *p;
8979             if (appData.debugMode) 
8980               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8981             p = yy_text;
8982             if (*p == '{' || *p == '[' || *p == '(') {
8983                 p[strlen(p) - 1] = NULLCHAR;
8984                 p++;
8985             }
8986             while (*p == '\n') p++;
8987             AppendComment(currentMove, p);
8988             yyboardindex = forwardMostMove;
8989             cm = (ChessMove) yylex();
8990         }
8991     }
8992
8993     /* don't rely on existence of Event tag since if game was
8994      * pasted from clipboard the Event tag may not exist
8995      */
8996     if (numPGNTags > 0){
8997         char *tags;
8998         if (gameInfo.variant == VariantNormal) {
8999           gameInfo.variant = StringToVariant(gameInfo.event);
9000         }
9001         if (!matchMode) {
9002           if( appData.autoDisplayTags ) {
9003             tags = PGNTags(&gameInfo);
9004             TagsPopUp(tags, CmailMsg());
9005             free(tags);
9006           }
9007         }
9008     } else {
9009         /* Make something up, but don't display it now */
9010         SetGameInfo();
9011         TagsPopDown();
9012     }
9013
9014     if (cm == PositionDiagram) {
9015         int i, j;
9016         char *p;
9017         Board initial_position;
9018
9019         if (appData.debugMode)
9020           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9021
9022         if (!startedFromSetupPosition) {
9023             p = yy_text;
9024             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9025               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9026                 switch (*p) {
9027                   case '[':
9028                   case '-':
9029                   case ' ':
9030                   case '\t':
9031                   case '\n':
9032                   case '\r':
9033                     break;
9034                   default:
9035                     initial_position[i][j++] = CharToPiece(*p);
9036                     break;
9037                 }
9038             while (*p == ' ' || *p == '\t' ||
9039                    *p == '\n' || *p == '\r') p++;
9040         
9041             if (strncmp(p, "black", strlen("black"))==0)
9042               blackPlaysFirst = TRUE;
9043             else
9044               blackPlaysFirst = FALSE;
9045             startedFromSetupPosition = TRUE;
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         }
9063         yyboardindex = forwardMostMove;
9064         cm = (ChessMove) yylex();
9065     }
9066
9067     if (first.pr == NoProc) {
9068         StartChessProgram(&first);
9069     }
9070     InitChessProgram(&first, FALSE);
9071     SendToProgram("force\n", &first);
9072     if (startedFromSetupPosition) {
9073         SendBoard(&first, forwardMostMove);
9074     if (appData.debugMode) {
9075         fprintf(debugFP, "Load Game\n");
9076     }
9077         DisplayBothClocks();
9078     }      
9079
9080     /* [HGM] server: flag to write setup moves in broadcast file as one */
9081     loadFlag = appData.suppressLoadMoves;
9082
9083     while (cm == Comment) {
9084         char *p;
9085         if (appData.debugMode) 
9086           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9087         p = yy_text;
9088         if (*p == '{' || *p == '[' || *p == '(') {
9089             p[strlen(p) - 1] = NULLCHAR;
9090             p++;
9091         }
9092         while (*p == '\n') p++;
9093         AppendComment(currentMove, p);
9094         yyboardindex = forwardMostMove;
9095         cm = (ChessMove) yylex();
9096     }
9097
9098     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9099         cm == WhiteWins || cm == BlackWins ||
9100         cm == GameIsDrawn || cm == GameUnfinished) {
9101         DisplayMessage("", _("No moves in game"));
9102         if (cmailMsgLoaded) {
9103             if (appData.debugMode)
9104               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9105             ClearHighlights();
9106             flipView = FALSE;
9107         }
9108         DrawPosition(FALSE, boards[currentMove]);
9109         DisplayBothClocks();
9110         gameMode = EditGame;
9111         ModeHighlight();
9112         gameFileFP = NULL;
9113         cmailOldMove = 0;
9114         return TRUE;
9115     }
9116
9117     // [HGM] PV info: routine tests if comment empty
9118     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9119         DisplayComment(currentMove - 1, commentList[currentMove]);
9120     }
9121     if (!matchMode && appData.timeDelay != 0) 
9122       DrawPosition(FALSE, boards[currentMove]);
9123
9124     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9125       programStats.ok_to_send = 1;
9126     }
9127
9128     /* if the first token after the PGN tags is a move
9129      * and not move number 1, retrieve it from the parser 
9130      */
9131     if (cm != MoveNumberOne)
9132         LoadGameOneMove(cm);
9133
9134     /* load the remaining moves from the file */
9135     while (LoadGameOneMove((ChessMove)0)) {
9136       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9137       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9138     }
9139
9140     /* rewind to the start of the game */
9141     currentMove = backwardMostMove;
9142
9143     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9144
9145     if (oldGameMode == AnalyzeFile ||
9146         oldGameMode == AnalyzeMode) {
9147       AnalyzeFileEvent();
9148     }
9149
9150     if (matchMode || appData.timeDelay == 0) {
9151       ToEndEvent();
9152       gameMode = EditGame;
9153       ModeHighlight();
9154     } else if (appData.timeDelay > 0) {
9155       AutoPlayGameLoop();
9156     }
9157
9158     if (appData.debugMode) 
9159         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9160
9161     loadFlag = 0; /* [HGM] true game starts */
9162     return TRUE;
9163 }
9164
9165 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9166 int
9167 ReloadPosition(offset)
9168      int offset;
9169 {
9170     int positionNumber = lastLoadPositionNumber + offset;
9171     if (lastLoadPositionFP == NULL) {
9172         DisplayError(_("No position has been loaded yet"), 0);
9173         return FALSE;
9174     }
9175     if (positionNumber <= 0) {
9176         DisplayError(_("Can't back up any further"), 0);
9177         return FALSE;
9178     }
9179     return LoadPosition(lastLoadPositionFP, positionNumber,
9180                         lastLoadPositionTitle);
9181 }
9182
9183 /* Load the nth position from the given file */
9184 int
9185 LoadPositionFromFile(filename, n, title)
9186      char *filename;
9187      int n;
9188      char *title;
9189 {
9190     FILE *f;
9191     char buf[MSG_SIZ];
9192
9193     if (strcmp(filename, "-") == 0) {
9194         return LoadPosition(stdin, n, "stdin");
9195     } else {
9196         f = fopen(filename, "rb");
9197         if (f == NULL) {
9198             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9199             DisplayError(buf, errno);
9200             return FALSE;
9201         } else {
9202             return LoadPosition(f, n, title);
9203         }
9204     }
9205 }
9206
9207 /* Load the nth position from the given open file, and close it */
9208 int
9209 LoadPosition(f, positionNumber, title)
9210      FILE *f;
9211      int positionNumber;
9212      char *title;
9213 {
9214     char *p, line[MSG_SIZ];
9215     Board initial_position;
9216     int i, j, fenMode, pn;
9217     
9218     if (gameMode == Training )
9219         SetTrainingModeOff();
9220
9221     if (gameMode != BeginningOfGame) {
9222         Reset(FALSE, TRUE);
9223     }
9224     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9225         fclose(lastLoadPositionFP);
9226     }
9227     if (positionNumber == 0) positionNumber = 1;
9228     lastLoadPositionFP = f;
9229     lastLoadPositionNumber = positionNumber;
9230     strcpy(lastLoadPositionTitle, title);
9231     if (first.pr == NoProc) {
9232       StartChessProgram(&first);
9233       InitChessProgram(&first, FALSE);
9234     }    
9235     pn = positionNumber;
9236     if (positionNumber < 0) {
9237         /* Negative position number means to seek to that byte offset */
9238         if (fseek(f, -positionNumber, 0) == -1) {
9239             DisplayError(_("Can't seek on position file"), 0);
9240             return FALSE;
9241         };
9242         pn = 1;
9243     } else {
9244         if (fseek(f, 0, 0) == -1) {
9245             if (f == lastLoadPositionFP ?
9246                 positionNumber == lastLoadPositionNumber + 1 :
9247                 positionNumber == 1) {
9248                 pn = 1;
9249             } else {
9250                 DisplayError(_("Can't seek on position file"), 0);
9251                 return FALSE;
9252             }
9253         }
9254     }
9255     /* See if this file is FEN or old-style xboard */
9256     if (fgets(line, MSG_SIZ, f) == NULL) {
9257         DisplayError(_("Position not found in file"), 0);
9258         return FALSE;
9259     }
9260 #if 0
9261     switch (line[0]) {
9262       case '#':  case 'x':
9263       default:
9264         fenMode = FALSE;
9265         break;
9266       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':
9267       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':
9268       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':
9269       case '7':  case '8':  case '9':
9270       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':
9271       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':
9272       case 'C':  case 'W':             case 'c':  case 'w': 
9273         fenMode = TRUE;
9274         break;
9275     }
9276 #else
9277     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9278     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9279 #endif
9280
9281     if (pn >= 2) {
9282         if (fenMode || line[0] == '#') pn--;
9283         while (pn > 0) {
9284             /* skip positions before number pn */
9285             if (fgets(line, MSG_SIZ, f) == NULL) {
9286                 Reset(TRUE, TRUE);
9287                 DisplayError(_("Position not found in file"), 0);
9288                 return FALSE;
9289             }
9290             if (fenMode || line[0] == '#') pn--;
9291         }
9292     }
9293
9294     if (fenMode) {
9295         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9296             DisplayError(_("Bad FEN position in file"), 0);
9297             return FALSE;
9298         }
9299     } else {
9300         (void) fgets(line, MSG_SIZ, f);
9301         (void) fgets(line, MSG_SIZ, f);
9302     
9303         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9304             (void) fgets(line, MSG_SIZ, f);
9305             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9306                 if (*p == ' ')
9307                   continue;
9308                 initial_position[i][j++] = CharToPiece(*p);
9309             }
9310         }
9311     
9312         blackPlaysFirst = FALSE;
9313         if (!feof(f)) {
9314             (void) fgets(line, MSG_SIZ, f);
9315             if (strncmp(line, "black", strlen("black"))==0)
9316               blackPlaysFirst = TRUE;
9317         }
9318     }
9319     startedFromSetupPosition = TRUE;
9320     
9321     SendToProgram("force\n", &first);
9322     CopyBoard(boards[0], initial_position);
9323     if (blackPlaysFirst) {
9324         currentMove = forwardMostMove = backwardMostMove = 1;
9325         strcpy(moveList[0], "");
9326         strcpy(parseList[0], "");
9327         CopyBoard(boards[1], initial_position);
9328         DisplayMessage("", _("Black to play"));
9329     } else {
9330         currentMove = forwardMostMove = backwardMostMove = 0;
9331         DisplayMessage("", _("White to play"));
9332     }
9333           /* [HGM] copy FEN attributes as well */
9334           {   int i;
9335               initialRulePlies = FENrulePlies;
9336               epStatus[forwardMostMove] = FENepStatus;
9337               for( i=0; i< nrCastlingRights; i++ )
9338                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9339           }
9340     SendBoard(&first, forwardMostMove);
9341     if (appData.debugMode) {
9342 int i, j;
9343   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9344   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9345         fprintf(debugFP, "Load Position\n");
9346     }
9347
9348     if (positionNumber > 1) {
9349         sprintf(line, "%s %d", title, positionNumber);
9350         DisplayTitle(line);
9351     } else {
9352         DisplayTitle(title);
9353     }
9354     gameMode = EditGame;
9355     ModeHighlight();
9356     ResetClocks();
9357     timeRemaining[0][1] = whiteTimeRemaining;
9358     timeRemaining[1][1] = blackTimeRemaining;
9359     DrawPosition(FALSE, boards[currentMove]);
9360    
9361     return TRUE;
9362 }
9363
9364
9365 void
9366 CopyPlayerNameIntoFileName(dest, src)
9367      char **dest, *src;
9368 {
9369     while (*src != NULLCHAR && *src != ',') {
9370         if (*src == ' ') {
9371             *(*dest)++ = '_';
9372             src++;
9373         } else {
9374             *(*dest)++ = *src++;
9375         }
9376     }
9377 }
9378
9379 char *DefaultFileName(ext)
9380      char *ext;
9381 {
9382     static char def[MSG_SIZ];
9383     char *p;
9384
9385     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9386         p = def;
9387         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9388         *p++ = '-';
9389         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9390         *p++ = '.';
9391         strcpy(p, ext);
9392     } else {
9393         def[0] = NULLCHAR;
9394     }
9395     return def;
9396 }
9397
9398 /* Save the current game to the given file */
9399 int
9400 SaveGameToFile(filename, append)
9401      char *filename;
9402      int append;
9403 {
9404     FILE *f;
9405     char buf[MSG_SIZ];
9406
9407     if (strcmp(filename, "-") == 0) {
9408         return SaveGame(stdout, 0, NULL);
9409     } else {
9410         f = fopen(filename, append ? "a" : "w");
9411         if (f == NULL) {
9412             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9413             DisplayError(buf, errno);
9414             return FALSE;
9415         } else {
9416             return SaveGame(f, 0, NULL);
9417         }
9418     }
9419 }
9420
9421 char *
9422 SavePart(str)
9423      char *str;
9424 {
9425     static char buf[MSG_SIZ];
9426     char *p;
9427     
9428     p = strchr(str, ' ');
9429     if (p == NULL) return str;
9430     strncpy(buf, str, p - str);
9431     buf[p - str] = NULLCHAR;
9432     return buf;
9433 }
9434
9435 #define PGN_MAX_LINE 75
9436
9437 #define PGN_SIDE_WHITE  0
9438 #define PGN_SIDE_BLACK  1
9439
9440 /* [AS] */
9441 static int FindFirstMoveOutOfBook( int side )
9442 {
9443     int result = -1;
9444
9445     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9446         int index = backwardMostMove;
9447         int has_book_hit = 0;
9448
9449         if( (index % 2) != side ) {
9450             index++;
9451         }
9452
9453         while( index < forwardMostMove ) {
9454             /* Check to see if engine is in book */
9455             int depth = pvInfoList[index].depth;
9456             int score = pvInfoList[index].score;
9457             int in_book = 0;
9458
9459             if( depth <= 2 ) {
9460                 in_book = 1;
9461             }
9462             else if( score == 0 && depth == 63 ) {
9463                 in_book = 1; /* Zappa */
9464             }
9465             else if( score == 2 && depth == 99 ) {
9466                 in_book = 1; /* Abrok */
9467             }
9468
9469             has_book_hit += in_book;
9470
9471             if( ! in_book ) {
9472                 result = index;
9473
9474                 break;
9475             }
9476
9477             index += 2;
9478         }
9479     }
9480
9481     return result;
9482 }
9483
9484 /* [AS] */
9485 void GetOutOfBookInfo( char * buf )
9486 {
9487     int oob[2];
9488     int i;
9489     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9490
9491     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9492     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9493
9494     *buf = '\0';
9495
9496     if( oob[0] >= 0 || oob[1] >= 0 ) {
9497         for( i=0; i<2; i++ ) {
9498             int idx = oob[i];
9499
9500             if( idx >= 0 ) {
9501                 if( i > 0 && oob[0] >= 0 ) {
9502                     strcat( buf, "   " );
9503                 }
9504
9505                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9506                 sprintf( buf+strlen(buf), "%s%.2f", 
9507                     pvInfoList[idx].score >= 0 ? "+" : "",
9508                     pvInfoList[idx].score / 100.0 );
9509             }
9510         }
9511     }
9512 }
9513
9514 /* Save game in PGN style and close the file */
9515 int
9516 SaveGamePGN(f)
9517      FILE *f;
9518 {
9519     int i, offset, linelen, newblock;
9520     time_t tm;
9521 //    char *movetext;
9522     char numtext[32];
9523     int movelen, numlen, blank;
9524     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9525
9526     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9527     
9528     tm = time((time_t *) NULL);
9529     
9530     PrintPGNTags(f, &gameInfo);
9531     
9532     if (backwardMostMove > 0 || startedFromSetupPosition) {
9533         char *fen = PositionToFEN(backwardMostMove, NULL);
9534         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9535         fprintf(f, "\n{--------------\n");
9536         PrintPosition(f, backwardMostMove);
9537         fprintf(f, "--------------}\n");
9538         free(fen);
9539     }
9540     else {
9541         /* [AS] Out of book annotation */
9542         if( appData.saveOutOfBookInfo ) {
9543             char buf[64];
9544
9545             GetOutOfBookInfo( buf );
9546
9547             if( buf[0] != '\0' ) {
9548                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9549             }
9550         }
9551
9552         fprintf(f, "\n");
9553     }
9554
9555     i = backwardMostMove;
9556     linelen = 0;
9557     newblock = TRUE;
9558
9559     while (i < forwardMostMove) {
9560         /* Print comments preceding this move */
9561         if (commentList[i] != NULL) {
9562             if (linelen > 0) fprintf(f, "\n");
9563             fprintf(f, "{\n%s}\n", commentList[i]);
9564             linelen = 0;
9565             newblock = TRUE;
9566         }
9567
9568         /* Format move number */
9569         if ((i % 2) == 0) {
9570             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9571         } else {
9572             if (newblock) {
9573                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9574             } else {
9575                 numtext[0] = NULLCHAR;
9576             }
9577         }
9578         numlen = strlen(numtext);
9579         newblock = FALSE;
9580
9581         /* Print move number */
9582         blank = linelen > 0 && numlen > 0;
9583         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9584             fprintf(f, "\n");
9585             linelen = 0;
9586             blank = 0;
9587         }
9588         if (blank) {
9589             fprintf(f, " ");
9590             linelen++;
9591         }
9592         fprintf(f, numtext);
9593         linelen += numlen;
9594
9595         /* Get move */
9596         strcpy(move_buffer, parseList[i]); // [HGM] pgn: print move via buffer, so it can be edited
9597         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9598         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9599                 int p = movelen - 1;
9600                 if(move_buffer[p] == ' ') p--;
9601                 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
9602                     while(p && move_buffer[--p] != '(');
9603                     if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
9604                 }
9605         }
9606
9607         /* Print move */
9608         blank = linelen > 0 && movelen > 0;
9609         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9610             fprintf(f, "\n");
9611             linelen = 0;
9612             blank = 0;
9613         }
9614         if (blank) {
9615             fprintf(f, " ");
9616             linelen++;
9617         }
9618         fprintf(f, move_buffer);
9619         linelen += movelen;
9620
9621         /* [AS] Add PV info if present */
9622         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9623             /* [HGM] add time */
9624             char buf[MSG_SIZ]; int seconds = 0;
9625
9626 #if 1
9627             if(i >= backwardMostMove) {
9628                 if(WhiteOnMove(i))
9629                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9630                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9631                 else
9632                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9633                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9634             }
9635             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9636 #else
9637             seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
9638 #endif
9639
9640             if( seconds <= 0) buf[0] = 0; else
9641             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9642                 seconds = (seconds + 4)/10; // round to full seconds
9643                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9644                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9645             }
9646
9647             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9648                 pvInfoList[i].score >= 0 ? "+" : "",
9649                 pvInfoList[i].score / 100.0,
9650                 pvInfoList[i].depth,
9651                 buf );
9652
9653             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9654
9655             /* Print score/depth */
9656             blank = linelen > 0 && movelen > 0;
9657             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9658                 fprintf(f, "\n");
9659                 linelen = 0;
9660                 blank = 0;
9661             }
9662             if (blank) {
9663                 fprintf(f, " ");
9664                 linelen++;
9665             }
9666             fprintf(f, move_buffer);
9667             linelen += movelen;
9668         }
9669
9670         i++;
9671     }
9672     
9673     /* Start a new line */
9674     if (linelen > 0) fprintf(f, "\n");
9675
9676     /* Print comments after last move */
9677     if (commentList[i] != NULL) {
9678         fprintf(f, "{\n%s}\n", commentList[i]);
9679     }
9680
9681     /* Print result */
9682     if (gameInfo.resultDetails != NULL &&
9683         gameInfo.resultDetails[0] != NULLCHAR) {
9684         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9685                 PGNResult(gameInfo.result));
9686     } else {
9687         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9688     }
9689
9690     fclose(f);
9691     return TRUE;
9692 }
9693
9694 /* Save game in old style and close the file */
9695 int
9696 SaveGameOldStyle(f)
9697      FILE *f;
9698 {
9699     int i, offset;
9700     time_t tm;
9701     
9702     tm = time((time_t *) NULL);
9703     
9704     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9705     PrintOpponents(f);
9706     
9707     if (backwardMostMove > 0 || startedFromSetupPosition) {
9708         fprintf(f, "\n[--------------\n");
9709         PrintPosition(f, backwardMostMove);
9710         fprintf(f, "--------------]\n");
9711     } else {
9712         fprintf(f, "\n");
9713     }
9714
9715     i = backwardMostMove;
9716     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9717
9718     while (i < forwardMostMove) {
9719         if (commentList[i] != NULL) {
9720             fprintf(f, "[%s]\n", commentList[i]);
9721         }
9722
9723         if ((i % 2) == 1) {
9724             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
9725             i++;
9726         } else {
9727             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
9728             i++;
9729             if (commentList[i] != NULL) {
9730                 fprintf(f, "\n");
9731                 continue;
9732             }
9733             if (i >= forwardMostMove) {
9734                 fprintf(f, "\n");
9735                 break;
9736             }
9737             fprintf(f, "%s\n", parseList[i]);
9738             i++;
9739         }
9740     }
9741     
9742     if (commentList[i] != NULL) {
9743         fprintf(f, "[%s]\n", commentList[i]);
9744     }
9745
9746     /* This isn't really the old style, but it's close enough */
9747     if (gameInfo.resultDetails != NULL &&
9748         gameInfo.resultDetails[0] != NULLCHAR) {
9749         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9750                 gameInfo.resultDetails);
9751     } else {
9752         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9753     }
9754
9755     fclose(f);
9756     return TRUE;
9757 }
9758
9759 /* Save the current game to open file f and close the file */
9760 int
9761 SaveGame(f, dummy, dummy2)
9762      FILE *f;
9763      int dummy;
9764      char *dummy2;
9765 {
9766     if (gameMode == EditPosition) EditPositionDone();
9767     if (appData.oldSaveStyle)
9768       return SaveGameOldStyle(f);
9769     else
9770       return SaveGamePGN(f);
9771 }
9772
9773 /* Save the current position to the given file */
9774 int
9775 SavePositionToFile(filename)
9776      char *filename;
9777 {
9778     FILE *f;
9779     char buf[MSG_SIZ];
9780
9781     if (strcmp(filename, "-") == 0) {
9782         return SavePosition(stdout, 0, NULL);
9783     } else {
9784         f = fopen(filename, "a");
9785         if (f == NULL) {
9786             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9787             DisplayError(buf, errno);
9788             return FALSE;
9789         } else {
9790             SavePosition(f, 0, NULL);
9791             return TRUE;
9792         }
9793     }
9794 }
9795
9796 /* Save the current position to the given open file and close the file */
9797 int
9798 SavePosition(f, dummy, dummy2)
9799      FILE *f;
9800      int dummy;
9801      char *dummy2;
9802 {
9803     time_t tm;
9804     char *fen;
9805     
9806     if (appData.oldSaveStyle) {
9807         tm = time((time_t *) NULL);
9808     
9809         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9810         PrintOpponents(f);
9811         fprintf(f, "[--------------\n");
9812         PrintPosition(f, currentMove);
9813         fprintf(f, "--------------]\n");
9814     } else {
9815         fen = PositionToFEN(currentMove, NULL);
9816         fprintf(f, "%s\n", fen);
9817         free(fen);
9818     }
9819     fclose(f);
9820     return TRUE;
9821 }
9822
9823 void
9824 ReloadCmailMsgEvent(unregister)
9825      int unregister;
9826 {
9827 #if !WIN32
9828     static char *inFilename = NULL;
9829     static char *outFilename;
9830     int i;
9831     struct stat inbuf, outbuf;
9832     int status;
9833     
9834     /* Any registered moves are unregistered if unregister is set, */
9835     /* i.e. invoked by the signal handler */
9836     if (unregister) {
9837         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9838             cmailMoveRegistered[i] = FALSE;
9839             if (cmailCommentList[i] != NULL) {
9840                 free(cmailCommentList[i]);
9841                 cmailCommentList[i] = NULL;
9842             }
9843         }
9844         nCmailMovesRegistered = 0;
9845     }
9846
9847     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9848         cmailResult[i] = CMAIL_NOT_RESULT;
9849     }
9850     nCmailResults = 0;
9851
9852     if (inFilename == NULL) {
9853         /* Because the filenames are static they only get malloced once  */
9854         /* and they never get freed                                      */
9855         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9856         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9857
9858         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9859         sprintf(outFilename, "%s.out", appData.cmailGameName);
9860     }
9861     
9862     status = stat(outFilename, &outbuf);
9863     if (status < 0) {
9864         cmailMailedMove = FALSE;
9865     } else {
9866         status = stat(inFilename, &inbuf);
9867         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9868     }
9869     
9870     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9871        counts the games, notes how each one terminated, etc.
9872        
9873        It would be nice to remove this kludge and instead gather all
9874        the information while building the game list.  (And to keep it
9875        in the game list nodes instead of having a bunch of fixed-size
9876        parallel arrays.)  Note this will require getting each game's
9877        termination from the PGN tags, as the game list builder does
9878        not process the game moves.  --mann
9879        */
9880     cmailMsgLoaded = TRUE;
9881     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9882     
9883     /* Load first game in the file or popup game menu */
9884     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9885
9886 #endif /* !WIN32 */
9887     return;
9888 }
9889
9890 int
9891 RegisterMove()
9892 {
9893     FILE *f;
9894     char string[MSG_SIZ];
9895
9896     if (   cmailMailedMove
9897         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9898         return TRUE;            /* Allow free viewing  */
9899     }
9900
9901     /* Unregister move to ensure that we don't leave RegisterMove        */
9902     /* with the move registered when the conditions for registering no   */
9903     /* longer hold                                                       */
9904     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9905         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9906         nCmailMovesRegistered --;
9907
9908         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
9909           {
9910               free(cmailCommentList[lastLoadGameNumber - 1]);
9911               cmailCommentList[lastLoadGameNumber - 1] = NULL;
9912           }
9913     }
9914
9915     if (cmailOldMove == -1) {
9916         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
9917         return FALSE;
9918     }
9919
9920     if (currentMove > cmailOldMove + 1) {
9921         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
9922         return FALSE;
9923     }
9924
9925     if (currentMove < cmailOldMove) {
9926         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
9927         return FALSE;
9928     }
9929
9930     if (forwardMostMove > currentMove) {
9931         /* Silently truncate extra moves */
9932         TruncateGame();
9933     }
9934
9935     if (   (currentMove == cmailOldMove + 1)
9936         || (   (currentMove == cmailOldMove)
9937             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
9938                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
9939         if (gameInfo.result != GameUnfinished) {
9940             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
9941         }
9942
9943         if (commentList[currentMove] != NULL) {
9944             cmailCommentList[lastLoadGameNumber - 1]
9945               = StrSave(commentList[currentMove]);
9946         }
9947         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
9948
9949         if (appData.debugMode)
9950           fprintf(debugFP, "Saving %s for game %d\n",
9951                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9952
9953         sprintf(string,
9954                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
9955         
9956         f = fopen(string, "w");
9957         if (appData.oldSaveStyle) {
9958             SaveGameOldStyle(f); /* also closes the file */
9959             
9960             sprintf(string, "%s.pos.out", appData.cmailGameName);
9961             f = fopen(string, "w");
9962             SavePosition(f, 0, NULL); /* also closes the file */
9963         } else {
9964             fprintf(f, "{--------------\n");
9965             PrintPosition(f, currentMove);
9966             fprintf(f, "--------------}\n\n");
9967             
9968             SaveGame(f, 0, NULL); /* also closes the file*/
9969         }
9970         
9971         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
9972         nCmailMovesRegistered ++;
9973     } else if (nCmailGames == 1) {
9974         DisplayError(_("You have not made a move yet"), 0);
9975         return FALSE;
9976     }
9977
9978     return TRUE;
9979 }
9980
9981 void
9982 MailMoveEvent()
9983 {
9984 #if !WIN32
9985     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
9986     FILE *commandOutput;
9987     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
9988     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
9989     int nBuffers;
9990     int i;
9991     int archived;
9992     char *arcDir;
9993
9994     if (! cmailMsgLoaded) {
9995         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
9996         return;
9997     }
9998
9999     if (nCmailGames == nCmailResults) {
10000         DisplayError(_("No unfinished games"), 0);
10001         return;
10002     }
10003
10004 #if CMAIL_PROHIBIT_REMAIL
10005     if (cmailMailedMove) {
10006         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);
10007         DisplayError(msg, 0);
10008         return;
10009     }
10010 #endif
10011
10012     if (! (cmailMailedMove || RegisterMove())) return;
10013     
10014     if (   cmailMailedMove
10015         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10016         sprintf(string, partCommandString,
10017                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10018         commandOutput = popen(string, "r");
10019
10020         if (commandOutput == NULL) {
10021             DisplayError(_("Failed to invoke cmail"), 0);
10022         } else {
10023             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10024                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10025             }
10026             if (nBuffers > 1) {
10027                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10028                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10029                 nBytes = MSG_SIZ - 1;
10030             } else {
10031                 (void) memcpy(msg, buffer, nBytes);
10032             }
10033             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10034
10035             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10036                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10037
10038                 archived = TRUE;
10039                 for (i = 0; i < nCmailGames; i ++) {
10040                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10041                         archived = FALSE;
10042                     }
10043                 }
10044                 if (   archived
10045                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10046                         != NULL)) {
10047                     sprintf(buffer, "%s/%s.%s.archive",
10048                             arcDir,
10049                             appData.cmailGameName,
10050                             gameInfo.date);
10051                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10052                     cmailMsgLoaded = FALSE;
10053                 }
10054             }
10055
10056             DisplayInformation(msg);
10057             pclose(commandOutput);
10058         }
10059     } else {
10060         if ((*cmailMsg) != '\0') {
10061             DisplayInformation(cmailMsg);
10062         }
10063     }
10064
10065     return;
10066 #endif /* !WIN32 */
10067 }
10068
10069 char *
10070 CmailMsg()
10071 {
10072 #if WIN32
10073     return NULL;
10074 #else
10075     int  prependComma = 0;
10076     char number[5];
10077     char string[MSG_SIZ];       /* Space for game-list */
10078     int  i;
10079     
10080     if (!cmailMsgLoaded) return "";
10081
10082     if (cmailMailedMove) {
10083         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10084     } else {
10085         /* Create a list of games left */
10086         sprintf(string, "[");
10087         for (i = 0; i < nCmailGames; i ++) {
10088             if (! (   cmailMoveRegistered[i]
10089                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10090                 if (prependComma) {
10091                     sprintf(number, ",%d", i + 1);
10092                 } else {
10093                     sprintf(number, "%d", i + 1);
10094                     prependComma = 1;
10095                 }
10096                 
10097                 strcat(string, number);
10098             }
10099         }
10100         strcat(string, "]");
10101
10102         if (nCmailMovesRegistered + nCmailResults == 0) {
10103             switch (nCmailGames) {
10104               case 1:
10105                 sprintf(cmailMsg,
10106                         _("Still need to make move for game\n"));
10107                 break;
10108                 
10109               case 2:
10110                 sprintf(cmailMsg,
10111                         _("Still need to make moves for both games\n"));
10112                 break;
10113                 
10114               default:
10115                 sprintf(cmailMsg,
10116                         _("Still need to make moves for all %d games\n"),
10117                         nCmailGames);
10118                 break;
10119             }
10120         } else {
10121             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10122               case 1:
10123                 sprintf(cmailMsg,
10124                         _("Still need to make a move for game %s\n"),
10125                         string);
10126                 break;
10127                 
10128               case 0:
10129                 if (nCmailResults == nCmailGames) {
10130                     sprintf(cmailMsg, _("No unfinished games\n"));
10131                 } else {
10132                     sprintf(cmailMsg, _("Ready to send mail\n"));
10133                 }
10134                 break;
10135                 
10136               default:
10137                 sprintf(cmailMsg,
10138                         _("Still need to make moves for games %s\n"),
10139                         string);
10140             }
10141         }
10142     }
10143     return cmailMsg;
10144 #endif /* WIN32 */
10145 }
10146
10147 void
10148 ResetGameEvent()
10149 {
10150     if (gameMode == Training)
10151       SetTrainingModeOff();
10152
10153     Reset(TRUE, TRUE);
10154     cmailMsgLoaded = FALSE;
10155     if (appData.icsActive) {
10156       SendToICS(ics_prefix);
10157       SendToICS("refresh\n");
10158     }
10159 }
10160
10161 void
10162 ExitEvent(status)
10163      int status;
10164 {
10165     exiting++;
10166     if (exiting > 2) {
10167       /* Give up on clean exit */
10168       exit(status);
10169     }
10170     if (exiting > 1) {
10171       /* Keep trying for clean exit */
10172       return;
10173     }
10174
10175     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10176
10177     if (telnetISR != NULL) {
10178       RemoveInputSource(telnetISR);
10179     }
10180     if (icsPR != NoProc) {
10181       DestroyChildProcess(icsPR, TRUE);
10182     }
10183 #if 0
10184     /* Save game if resource set and not already saved by GameEnds() */
10185     if ((gameInfo.resultDetails == NULL || errorExitFlag )
10186                              && forwardMostMove > 0) {
10187       if (*appData.saveGameFile != NULLCHAR) {
10188         SaveGameToFile(appData.saveGameFile, TRUE);
10189       } else if (appData.autoSaveGames) {
10190         AutoSaveGame();
10191       }
10192       if (*appData.savePositionFile != NULLCHAR) {
10193         SavePositionToFile(appData.savePositionFile);
10194       }
10195     }
10196     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10197 #else
10198     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10199     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10200 #endif
10201     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10202     /* make sure this other one finishes before killing it!                  */
10203     if(endingGame) { int count = 0;
10204         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10205         while(endingGame && count++ < 10) DoSleep(1);
10206         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10207     }
10208
10209     /* Kill off chess programs */
10210     if (first.pr != NoProc) {
10211         ExitAnalyzeMode();
10212         
10213         DoSleep( appData.delayBeforeQuit );
10214         SendToProgram("quit\n", &first);
10215         DoSleep( appData.delayAfterQuit );
10216         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10217     }
10218     if (second.pr != NoProc) {
10219         DoSleep( appData.delayBeforeQuit );
10220         SendToProgram("quit\n", &second);
10221         DoSleep( appData.delayAfterQuit );
10222         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10223     }
10224     if (first.isr != NULL) {
10225         RemoveInputSource(first.isr);
10226     }
10227     if (second.isr != NULL) {
10228         RemoveInputSource(second.isr);
10229     }
10230
10231     ShutDownFrontEnd();
10232     exit(status);
10233 }
10234
10235 void
10236 PauseEvent()
10237 {
10238     if (appData.debugMode)
10239         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10240     if (pausing) {
10241         pausing = FALSE;
10242         ModeHighlight();
10243         if (gameMode == MachinePlaysWhite ||
10244             gameMode == MachinePlaysBlack) {
10245             StartClocks();
10246         } else {
10247             DisplayBothClocks();
10248         }
10249         if (gameMode == PlayFromGameFile) {
10250             if (appData.timeDelay >= 0) 
10251                 AutoPlayGameLoop();
10252         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10253             Reset(FALSE, TRUE);
10254             SendToICS(ics_prefix);
10255             SendToICS("refresh\n");
10256         } else if (currentMove < forwardMostMove) {
10257             ForwardInner(forwardMostMove);
10258         }
10259         pauseExamInvalid = FALSE;
10260     } else {
10261         switch (gameMode) {
10262           default:
10263             return;
10264           case IcsExamining:
10265             pauseExamForwardMostMove = forwardMostMove;
10266             pauseExamInvalid = FALSE;
10267             /* fall through */
10268           case IcsObserving:
10269           case IcsPlayingWhite:
10270           case IcsPlayingBlack:
10271             pausing = TRUE;
10272             ModeHighlight();
10273             return;
10274           case PlayFromGameFile:
10275             (void) StopLoadGameTimer();
10276             pausing = TRUE;
10277             ModeHighlight();
10278             break;
10279           case BeginningOfGame:
10280             if (appData.icsActive) return;
10281             /* else fall through */
10282           case MachinePlaysWhite:
10283           case MachinePlaysBlack:
10284           case TwoMachinesPlay:
10285             if (forwardMostMove == 0)
10286               return;           /* don't pause if no one has moved */
10287             if ((gameMode == MachinePlaysWhite &&
10288                  !WhiteOnMove(forwardMostMove)) ||
10289                 (gameMode == MachinePlaysBlack &&
10290                  WhiteOnMove(forwardMostMove))) {
10291                 StopClocks();
10292             }
10293             pausing = TRUE;
10294             ModeHighlight();
10295             break;
10296         }
10297     }
10298 }
10299
10300 void
10301 EditCommentEvent()
10302 {
10303     char title[MSG_SIZ];
10304
10305     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10306         strcpy(title, _("Edit comment"));
10307     } else {
10308         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10309                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10310                 parseList[currentMove - 1]);
10311     }
10312
10313     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10314 }
10315
10316
10317 void
10318 EditTagsEvent()
10319 {
10320     char *tags = PGNTags(&gameInfo);
10321     EditTagsPopUp(tags);
10322     free(tags);
10323 }
10324
10325 void
10326 AnalyzeModeEvent()
10327 {
10328     if (appData.noChessProgram || gameMode == AnalyzeMode)
10329       return;
10330
10331     if (gameMode != AnalyzeFile) {
10332         if (!appData.icsEngineAnalyze) {
10333                EditGameEvent();
10334                if (gameMode != EditGame) return;
10335         }
10336         ResurrectChessProgram();
10337         SendToProgram("analyze\n", &first);
10338         first.analyzing = TRUE;
10339         /*first.maybeThinking = TRUE;*/
10340         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10341         AnalysisPopUp(_("Analysis"),
10342                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10343     }
10344     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10345     pausing = FALSE;
10346     ModeHighlight();
10347     SetGameInfo();
10348
10349     StartAnalysisClock();
10350     GetTimeMark(&lastNodeCountTime);
10351     lastNodeCount = 0;
10352 }
10353
10354 void
10355 AnalyzeFileEvent()
10356 {
10357     if (appData.noChessProgram || gameMode == AnalyzeFile)
10358       return;
10359
10360     if (gameMode != AnalyzeMode) {
10361         EditGameEvent();
10362         if (gameMode != EditGame) return;
10363         ResurrectChessProgram();
10364         SendToProgram("analyze\n", &first);
10365         first.analyzing = TRUE;
10366         /*first.maybeThinking = TRUE;*/
10367         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10368         AnalysisPopUp(_("Analysis"),
10369                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10370     }
10371     gameMode = AnalyzeFile;
10372     pausing = FALSE;
10373     ModeHighlight();
10374     SetGameInfo();
10375
10376     StartAnalysisClock();
10377     GetTimeMark(&lastNodeCountTime);
10378     lastNodeCount = 0;
10379 }
10380
10381 void
10382 MachineWhiteEvent()
10383 {
10384     char buf[MSG_SIZ];
10385     char *bookHit = NULL;
10386
10387     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10388       return;
10389
10390
10391     if (gameMode == PlayFromGameFile || 
10392         gameMode == TwoMachinesPlay  || 
10393         gameMode == Training         || 
10394         gameMode == AnalyzeMode      || 
10395         gameMode == EndOfGame)
10396         EditGameEvent();
10397
10398     if (gameMode == EditPosition) 
10399         EditPositionDone();
10400
10401     if (!WhiteOnMove(currentMove)) {
10402         DisplayError(_("It is not White's turn"), 0);
10403         return;
10404     }
10405   
10406     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10407       ExitAnalyzeMode();
10408
10409     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10410         gameMode == AnalyzeFile)
10411         TruncateGame();
10412
10413     ResurrectChessProgram();    /* in case it isn't running */
10414     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10415         gameMode = MachinePlaysWhite;
10416         ResetClocks();
10417     } else
10418     gameMode = MachinePlaysWhite;
10419     pausing = FALSE;
10420     ModeHighlight();
10421     SetGameInfo();
10422     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10423     DisplayTitle(buf);
10424     if (first.sendName) {
10425       sprintf(buf, "name %s\n", gameInfo.black);
10426       SendToProgram(buf, &first);
10427     }
10428     if (first.sendTime) {
10429       if (first.useColors) {
10430         SendToProgram("black\n", &first); /*gnu kludge*/
10431       }
10432       SendTimeRemaining(&first, TRUE);
10433     }
10434     if (first.useColors) {
10435       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10436     }
10437     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10438     SetMachineThinkingEnables();
10439     first.maybeThinking = TRUE;
10440     StartClocks();
10441
10442     if (appData.autoFlipView && !flipView) {
10443       flipView = !flipView;
10444       DrawPosition(FALSE, NULL);
10445       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10446     }
10447
10448     if(bookHit) { // [HGM] book: simulate book reply
10449         static char bookMove[MSG_SIZ]; // a bit generous?
10450
10451         programStats.nodes = programStats.depth = programStats.time = 
10452         programStats.score = programStats.got_only_move = 0;
10453         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10454
10455         strcpy(bookMove, "move ");
10456         strcat(bookMove, bookHit);
10457         HandleMachineMove(bookMove, &first);
10458     }
10459 }
10460
10461 void
10462 MachineBlackEvent()
10463 {
10464     char buf[MSG_SIZ];
10465    char *bookHit = NULL;
10466
10467     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10468         return;
10469
10470
10471     if (gameMode == PlayFromGameFile || 
10472         gameMode == TwoMachinesPlay  || 
10473         gameMode == Training         || 
10474         gameMode == AnalyzeMode      || 
10475         gameMode == EndOfGame)
10476         EditGameEvent();
10477
10478     if (gameMode == EditPosition) 
10479         EditPositionDone();
10480
10481     if (WhiteOnMove(currentMove)) {
10482         DisplayError(_("It is not Black's turn"), 0);
10483         return;
10484     }
10485     
10486     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10487       ExitAnalyzeMode();
10488
10489     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10490         gameMode == AnalyzeFile)
10491         TruncateGame();
10492
10493     ResurrectChessProgram();    /* in case it isn't running */
10494     gameMode = MachinePlaysBlack;
10495     pausing = FALSE;
10496     ModeHighlight();
10497     SetGameInfo();
10498     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10499     DisplayTitle(buf);
10500     if (first.sendName) {
10501       sprintf(buf, "name %s\n", gameInfo.white);
10502       SendToProgram(buf, &first);
10503     }
10504     if (first.sendTime) {
10505       if (first.useColors) {
10506         SendToProgram("white\n", &first); /*gnu kludge*/
10507       }
10508       SendTimeRemaining(&first, FALSE);
10509     }
10510     if (first.useColors) {
10511       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10512     }
10513     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10514     SetMachineThinkingEnables();
10515     first.maybeThinking = TRUE;
10516     StartClocks();
10517
10518     if (appData.autoFlipView && flipView) {
10519       flipView = !flipView;
10520       DrawPosition(FALSE, NULL);
10521       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10522     }
10523     if(bookHit) { // [HGM] book: simulate book reply
10524         static char bookMove[MSG_SIZ]; // a bit generous?
10525
10526         programStats.nodes = programStats.depth = programStats.time = 
10527         programStats.score = programStats.got_only_move = 0;
10528         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10529
10530         strcpy(bookMove, "move ");
10531         strcat(bookMove, bookHit);
10532         HandleMachineMove(bookMove, &first);
10533     }
10534 }
10535
10536
10537 void
10538 DisplayTwoMachinesTitle()
10539 {
10540     char buf[MSG_SIZ];
10541     if (appData.matchGames > 0) {
10542         if (first.twoMachinesColor[0] == 'w') {
10543             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10544                     gameInfo.white, gameInfo.black,
10545                     first.matchWins, second.matchWins,
10546                     matchGame - 1 - (first.matchWins + second.matchWins));
10547         } else {
10548             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10549                     gameInfo.white, gameInfo.black,
10550                     second.matchWins, first.matchWins,
10551                     matchGame - 1 - (first.matchWins + second.matchWins));
10552         }
10553     } else {
10554         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10555     }
10556     DisplayTitle(buf);
10557 }
10558
10559 void
10560 TwoMachinesEvent P((void))
10561 {
10562     int i;
10563     char buf[MSG_SIZ];
10564     ChessProgramState *onmove;
10565     char *bookHit = NULL;
10566     
10567     if (appData.noChessProgram) return;
10568
10569     switch (gameMode) {
10570       case TwoMachinesPlay:
10571         return;
10572       case MachinePlaysWhite:
10573       case MachinePlaysBlack:
10574         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10575             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10576             return;
10577         }
10578         /* fall through */
10579       case BeginningOfGame:
10580       case PlayFromGameFile:
10581       case EndOfGame:
10582         EditGameEvent();
10583         if (gameMode != EditGame) return;
10584         break;
10585       case EditPosition:
10586         EditPositionDone();
10587         break;
10588       case AnalyzeMode:
10589       case AnalyzeFile:
10590         ExitAnalyzeMode();
10591         break;
10592       case EditGame:
10593       default:
10594         break;
10595     }
10596
10597     forwardMostMove = currentMove;
10598     ResurrectChessProgram();    /* in case first program isn't running */
10599
10600     if (second.pr == NULL) {
10601         StartChessProgram(&second);
10602         if (second.protocolVersion == 1) {
10603           TwoMachinesEventIfReady();
10604         } else {
10605           /* kludge: allow timeout for initial "feature" command */
10606           FreezeUI();
10607           DisplayMessage("", _("Starting second chess program"));
10608           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10609         }
10610         return;
10611     }
10612     DisplayMessage("", "");
10613     InitChessProgram(&second, FALSE);
10614     SendToProgram("force\n", &second);
10615     if (startedFromSetupPosition) {
10616         SendBoard(&second, backwardMostMove);
10617     if (appData.debugMode) {
10618         fprintf(debugFP, "Two Machines\n");
10619     }
10620     }
10621     for (i = backwardMostMove; i < forwardMostMove; i++) {
10622         SendMoveToProgram(i, &second);
10623     }
10624
10625     gameMode = TwoMachinesPlay;
10626     pausing = FALSE;
10627     ModeHighlight();
10628     SetGameInfo();
10629     DisplayTwoMachinesTitle();
10630     firstMove = TRUE;
10631     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10632         onmove = &first;
10633     } else {
10634         onmove = &second;
10635     }
10636
10637     SendToProgram(first.computerString, &first);
10638     if (first.sendName) {
10639       sprintf(buf, "name %s\n", second.tidy);
10640       SendToProgram(buf, &first);
10641     }
10642     SendToProgram(second.computerString, &second);
10643     if (second.sendName) {
10644       sprintf(buf, "name %s\n", first.tidy);
10645       SendToProgram(buf, &second);
10646     }
10647
10648     ResetClocks();
10649     if (!first.sendTime || !second.sendTime) {
10650         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10651         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10652     }
10653     if (onmove->sendTime) {
10654       if (onmove->useColors) {
10655         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10656       }
10657       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10658     }
10659     if (onmove->useColors) {
10660       SendToProgram(onmove->twoMachinesColor, onmove);
10661     }
10662     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10663 //    SendToProgram("go\n", onmove);
10664     onmove->maybeThinking = TRUE;
10665     SetMachineThinkingEnables();
10666
10667     StartClocks();
10668
10669     if(bookHit) { // [HGM] book: simulate book reply
10670         static char bookMove[MSG_SIZ]; // a bit generous?
10671
10672         programStats.nodes = programStats.depth = programStats.time = 
10673         programStats.score = programStats.got_only_move = 0;
10674         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10675
10676         strcpy(bookMove, "move ");
10677         strcat(bookMove, bookHit);
10678         HandleMachineMove(bookMove, &first);
10679     }
10680 }
10681
10682 void
10683 TrainingEvent()
10684 {
10685     if (gameMode == Training) {
10686       SetTrainingModeOff();
10687       gameMode = PlayFromGameFile;
10688       DisplayMessage("", _("Training mode off"));
10689     } else {
10690       gameMode = Training;
10691       animateTraining = appData.animate;
10692
10693       /* make sure we are not already at the end of the game */
10694       if (currentMove < forwardMostMove) {
10695         SetTrainingModeOn();
10696         DisplayMessage("", _("Training mode on"));
10697       } else {
10698         gameMode = PlayFromGameFile;
10699         DisplayError(_("Already at end of game"), 0);
10700       }
10701     }
10702     ModeHighlight();
10703 }
10704
10705 void
10706 IcsClientEvent()
10707 {
10708     if (!appData.icsActive) return;
10709     switch (gameMode) {
10710       case IcsPlayingWhite:
10711       case IcsPlayingBlack:
10712       case IcsObserving:
10713       case IcsIdle:
10714       case BeginningOfGame:
10715       case IcsExamining:
10716         return;
10717
10718       case EditGame:
10719         break;
10720
10721       case EditPosition:
10722         EditPositionDone();
10723         break;
10724
10725       case AnalyzeMode:
10726       case AnalyzeFile:
10727         ExitAnalyzeMode();
10728         break;
10729         
10730       default:
10731         EditGameEvent();
10732         break;
10733     }
10734
10735     gameMode = IcsIdle;
10736     ModeHighlight();
10737     return;
10738 }
10739
10740
10741 void
10742 EditGameEvent()
10743 {
10744     int i;
10745
10746     switch (gameMode) {
10747       case Training:
10748         SetTrainingModeOff();
10749         break;
10750       case MachinePlaysWhite:
10751       case MachinePlaysBlack:
10752       case BeginningOfGame:
10753         SendToProgram("force\n", &first);
10754         SetUserThinkingEnables();
10755         break;
10756       case PlayFromGameFile:
10757         (void) StopLoadGameTimer();
10758         if (gameFileFP != NULL) {
10759             gameFileFP = NULL;
10760         }
10761         break;
10762       case EditPosition:
10763         EditPositionDone();
10764         break;
10765       case AnalyzeMode:
10766       case AnalyzeFile:
10767         ExitAnalyzeMode();
10768         SendToProgram("force\n", &first);
10769         break;
10770       case TwoMachinesPlay:
10771         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10772         ResurrectChessProgram();
10773         SetUserThinkingEnables();
10774         break;
10775       case EndOfGame:
10776         ResurrectChessProgram();
10777         break;
10778       case IcsPlayingBlack:
10779       case IcsPlayingWhite:
10780         DisplayError(_("Warning: You are still playing a game"), 0);
10781         break;
10782       case IcsObserving:
10783         DisplayError(_("Warning: You are still observing a game"), 0);
10784         break;
10785       case IcsExamining:
10786         DisplayError(_("Warning: You are still examining a game"), 0);
10787         break;
10788       case IcsIdle:
10789         break;
10790       case EditGame:
10791       default:
10792         return;
10793     }
10794     
10795     pausing = FALSE;
10796     StopClocks();
10797     first.offeredDraw = second.offeredDraw = 0;
10798
10799     if (gameMode == PlayFromGameFile) {
10800         whiteTimeRemaining = timeRemaining[0][currentMove];
10801         blackTimeRemaining = timeRemaining[1][currentMove];
10802         DisplayTitle("");
10803     }
10804
10805     if (gameMode == MachinePlaysWhite ||
10806         gameMode == MachinePlaysBlack ||
10807         gameMode == TwoMachinesPlay ||
10808         gameMode == EndOfGame) {
10809         i = forwardMostMove;
10810         while (i > currentMove) {
10811             SendToProgram("undo\n", &first);
10812             i--;
10813         }
10814         whiteTimeRemaining = timeRemaining[0][currentMove];
10815         blackTimeRemaining = timeRemaining[1][currentMove];
10816         DisplayBothClocks();
10817         if (whiteFlag || blackFlag) {
10818             whiteFlag = blackFlag = 0;
10819         }
10820         DisplayTitle("");
10821     }           
10822     
10823     gameMode = EditGame;
10824     ModeHighlight();
10825     SetGameInfo();
10826 }
10827
10828
10829 void
10830 EditPositionEvent()
10831 {
10832     if (gameMode == EditPosition) {
10833         EditGameEvent();
10834         return;
10835     }
10836     
10837     EditGameEvent();
10838     if (gameMode != EditGame) return;
10839     
10840     gameMode = EditPosition;
10841     ModeHighlight();
10842     SetGameInfo();
10843     if (currentMove > 0)
10844       CopyBoard(boards[0], boards[currentMove]);
10845     
10846     blackPlaysFirst = !WhiteOnMove(currentMove);
10847     ResetClocks();
10848     currentMove = forwardMostMove = backwardMostMove = 0;
10849     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10850     DisplayMove(-1);
10851 }
10852
10853 void
10854 ExitAnalyzeMode()
10855 {
10856     /* [DM] icsEngineAnalyze - possible call from other functions */
10857     if (appData.icsEngineAnalyze) {
10858         appData.icsEngineAnalyze = FALSE;
10859
10860         DisplayMessage("",_("Close ICS engine analyze..."));
10861     }
10862     if (first.analysisSupport && first.analyzing) {
10863       SendToProgram("exit\n", &first);
10864       first.analyzing = FALSE;
10865     }
10866     AnalysisPopDown();
10867     thinkOutput[0] = NULLCHAR;
10868 }
10869
10870 void
10871 EditPositionDone()
10872 {
10873     startedFromSetupPosition = TRUE;
10874     InitChessProgram(&first, FALSE);
10875     SendToProgram("force\n", &first);
10876     if (blackPlaysFirst) {
10877         strcpy(moveList[0], "");
10878         strcpy(parseList[0], "");
10879         currentMove = forwardMostMove = backwardMostMove = 1;
10880         CopyBoard(boards[1], boards[0]);
10881         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10882         { int i;
10883           epStatus[1] = epStatus[0];
10884           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10885         }
10886     } else {
10887         currentMove = forwardMostMove = backwardMostMove = 0;
10888     }
10889     SendBoard(&first, forwardMostMove);
10890     if (appData.debugMode) {
10891         fprintf(debugFP, "EditPosDone\n");
10892     }
10893     DisplayTitle("");
10894     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10895     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10896     gameMode = EditGame;
10897     ModeHighlight();
10898     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10899     ClearHighlights(); /* [AS] */
10900 }
10901
10902 /* Pause for `ms' milliseconds */
10903 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10904 void
10905 TimeDelay(ms)
10906      long ms;
10907 {
10908     TimeMark m1, m2;
10909
10910     GetTimeMark(&m1);
10911     do {
10912         GetTimeMark(&m2);
10913     } while (SubtractTimeMarks(&m2, &m1) < ms);
10914 }
10915
10916 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10917 void
10918 SendMultiLineToICS(buf)
10919      char *buf;
10920 {
10921     char temp[MSG_SIZ+1], *p;
10922     int len;
10923
10924     len = strlen(buf);
10925     if (len > MSG_SIZ)
10926       len = MSG_SIZ;
10927   
10928     strncpy(temp, buf, len);
10929     temp[len] = 0;
10930
10931     p = temp;
10932     while (*p) {
10933         if (*p == '\n' || *p == '\r')
10934           *p = ' ';
10935         ++p;
10936     }
10937
10938     strcat(temp, "\n");
10939     SendToICS(temp);
10940     SendToPlayer(temp, strlen(temp));
10941 }
10942
10943 void
10944 SetWhiteToPlayEvent()
10945 {
10946     if (gameMode == EditPosition) {
10947         blackPlaysFirst = FALSE;
10948         DisplayBothClocks();    /* works because currentMove is 0 */
10949     } else if (gameMode == IcsExamining) {
10950         SendToICS(ics_prefix);
10951         SendToICS("tomove white\n");
10952     }
10953 }
10954
10955 void
10956 SetBlackToPlayEvent()
10957 {
10958     if (gameMode == EditPosition) {
10959         blackPlaysFirst = TRUE;
10960         currentMove = 1;        /* kludge */
10961         DisplayBothClocks();
10962         currentMove = 0;
10963     } else if (gameMode == IcsExamining) {
10964         SendToICS(ics_prefix);
10965         SendToICS("tomove black\n");
10966     }
10967 }
10968
10969 void
10970 EditPositionMenuEvent(selection, x, y)
10971      ChessSquare selection;
10972      int x, y;
10973 {
10974     char buf[MSG_SIZ];
10975     ChessSquare piece = boards[0][y][x];
10976
10977     if (gameMode != EditPosition && gameMode != IcsExamining) return;
10978
10979     switch (selection) {
10980       case ClearBoard:
10981         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
10982             SendToICS(ics_prefix);
10983             SendToICS("bsetup clear\n");
10984         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
10985             SendToICS(ics_prefix);
10986             SendToICS("clearboard\n");
10987         } else {
10988             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
10989                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
10990                 for (y = 0; y < BOARD_HEIGHT; y++) {
10991                     if (gameMode == IcsExamining) {
10992                         if (boards[currentMove][y][x] != EmptySquare) {
10993                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
10994                                     AAA + x, ONE + y);
10995                             SendToICS(buf);
10996                         }
10997                     } else {
10998                         boards[0][y][x] = p;
10999                     }
11000                 }
11001             }
11002         }
11003         if (gameMode == EditPosition) {
11004             DrawPosition(FALSE, boards[0]);
11005         }
11006         break;
11007
11008       case WhitePlay:
11009         SetWhiteToPlayEvent();
11010         break;
11011
11012       case BlackPlay:
11013         SetBlackToPlayEvent();
11014         break;
11015
11016       case EmptySquare:
11017         if (gameMode == IcsExamining) {
11018             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11019             SendToICS(buf);
11020         } else {
11021             boards[0][y][x] = EmptySquare;
11022             DrawPosition(FALSE, boards[0]);
11023         }
11024         break;
11025
11026       case PromotePiece:
11027         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11028            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11029             selection = (ChessSquare) (PROMOTED piece);
11030         } else if(piece == EmptySquare) selection = WhiteSilver;
11031         else selection = (ChessSquare)((int)piece - 1);
11032         goto defaultlabel;
11033
11034       case DemotePiece:
11035         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11036            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11037             selection = (ChessSquare) (DEMOTED piece);
11038         } else if(piece == EmptySquare) selection = BlackSilver;
11039         else selection = (ChessSquare)((int)piece + 1);       
11040         goto defaultlabel;
11041
11042       case WhiteQueen:
11043       case BlackQueen:
11044         if(gameInfo.variant == VariantShatranj ||
11045            gameInfo.variant == VariantXiangqi  ||
11046            gameInfo.variant == VariantCourier    )
11047             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11048         goto defaultlabel;
11049
11050       case WhiteKing:
11051       case BlackKing:
11052         if(gameInfo.variant == VariantXiangqi)
11053             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11054         if(gameInfo.variant == VariantKnightmate)
11055             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11056       default:
11057         defaultlabel:
11058         if (gameMode == IcsExamining) {
11059             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11060                     PieceToChar(selection), AAA + x, ONE + y);
11061             SendToICS(buf);
11062         } else {
11063             boards[0][y][x] = selection;
11064             DrawPosition(FALSE, boards[0]);
11065         }
11066         break;
11067     }
11068 }
11069
11070
11071 void
11072 DropMenuEvent(selection, x, y)
11073      ChessSquare selection;
11074      int x, y;
11075 {
11076     ChessMove moveType;
11077
11078     switch (gameMode) {
11079       case IcsPlayingWhite:
11080       case MachinePlaysBlack:
11081         if (!WhiteOnMove(currentMove)) {
11082             DisplayMoveError(_("It is Black's turn"));
11083             return;
11084         }
11085         moveType = WhiteDrop;
11086         break;
11087       case IcsPlayingBlack:
11088       case MachinePlaysWhite:
11089         if (WhiteOnMove(currentMove)) {
11090             DisplayMoveError(_("It is White's turn"));
11091             return;
11092         }
11093         moveType = BlackDrop;
11094         break;
11095       case EditGame:
11096         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11097         break;
11098       default:
11099         return;
11100     }
11101
11102     if (moveType == BlackDrop && selection < BlackPawn) {
11103       selection = (ChessSquare) ((int) selection
11104                                  + (int) BlackPawn - (int) WhitePawn);
11105     }
11106     if (boards[currentMove][y][x] != EmptySquare) {
11107         DisplayMoveError(_("That square is occupied"));
11108         return;
11109     }
11110
11111     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11112 }
11113
11114 void
11115 AcceptEvent()
11116 {
11117     /* Accept a pending offer of any kind from opponent */
11118     
11119     if (appData.icsActive) {
11120         SendToICS(ics_prefix);
11121         SendToICS("accept\n");
11122     } else if (cmailMsgLoaded) {
11123         if (currentMove == cmailOldMove &&
11124             commentList[cmailOldMove] != NULL &&
11125             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11126                    "Black offers a draw" : "White offers a draw")) {
11127             TruncateGame();
11128             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11129             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11130         } else {
11131             DisplayError(_("There is no pending offer on this move"), 0);
11132             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11133         }
11134     } else {
11135         /* Not used for offers from chess program */
11136     }
11137 }
11138
11139 void
11140 DeclineEvent()
11141 {
11142     /* Decline a pending offer of any kind from opponent */
11143     
11144     if (appData.icsActive) {
11145         SendToICS(ics_prefix);
11146         SendToICS("decline\n");
11147     } else if (cmailMsgLoaded) {
11148         if (currentMove == cmailOldMove &&
11149             commentList[cmailOldMove] != NULL &&
11150             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11151                    "Black offers a draw" : "White offers a draw")) {
11152 #ifdef NOTDEF
11153             AppendComment(cmailOldMove, "Draw declined");
11154             DisplayComment(cmailOldMove - 1, "Draw declined");
11155 #endif /*NOTDEF*/
11156         } else {
11157             DisplayError(_("There is no pending offer on this move"), 0);
11158         }
11159     } else {
11160         /* Not used for offers from chess program */
11161     }
11162 }
11163
11164 void
11165 RematchEvent()
11166 {
11167     /* Issue ICS rematch command */
11168     if (appData.icsActive) {
11169         SendToICS(ics_prefix);
11170         SendToICS("rematch\n");
11171     }
11172 }
11173
11174 void
11175 CallFlagEvent()
11176 {
11177     /* Call your opponent's flag (claim a win on time) */
11178     if (appData.icsActive) {
11179         SendToICS(ics_prefix);
11180         SendToICS("flag\n");
11181     } else {
11182         switch (gameMode) {
11183           default:
11184             return;
11185           case MachinePlaysWhite:
11186             if (whiteFlag) {
11187                 if (blackFlag)
11188                   GameEnds(GameIsDrawn, "Both players ran out of time",
11189                            GE_PLAYER);
11190                 else
11191                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11192             } else {
11193                 DisplayError(_("Your opponent is not out of time"), 0);
11194             }
11195             break;
11196           case MachinePlaysBlack:
11197             if (blackFlag) {
11198                 if (whiteFlag)
11199                   GameEnds(GameIsDrawn, "Both players ran out of time",
11200                            GE_PLAYER);
11201                 else
11202                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11203             } else {
11204                 DisplayError(_("Your opponent is not out of time"), 0);
11205             }
11206             break;
11207         }
11208     }
11209 }
11210
11211 void
11212 DrawEvent()
11213 {
11214     /* Offer draw or accept pending draw offer from opponent */
11215     
11216     if (appData.icsActive) {
11217         /* Note: tournament rules require draw offers to be
11218            made after you make your move but before you punch
11219            your clock.  Currently ICS doesn't let you do that;
11220            instead, you immediately punch your clock after making
11221            a move, but you can offer a draw at any time. */
11222         
11223         SendToICS(ics_prefix);
11224         SendToICS("draw\n");
11225     } else if (cmailMsgLoaded) {
11226         if (currentMove == cmailOldMove &&
11227             commentList[cmailOldMove] != NULL &&
11228             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11229                    "Black offers a draw" : "White offers a draw")) {
11230             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11231             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11232         } else if (currentMove == cmailOldMove + 1) {
11233             char *offer = WhiteOnMove(cmailOldMove) ?
11234               "White offers a draw" : "Black offers a draw";
11235             AppendComment(currentMove, offer);
11236             DisplayComment(currentMove - 1, offer);
11237             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11238         } else {
11239             DisplayError(_("You must make your move before offering a draw"), 0);
11240             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11241         }
11242     } else if (first.offeredDraw) {
11243         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11244     } else {
11245         if (first.sendDrawOffers) {
11246             SendToProgram("draw\n", &first);
11247             userOfferedDraw = TRUE;
11248         }
11249     }
11250 }
11251
11252 void
11253 AdjournEvent()
11254 {
11255     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11256     
11257     if (appData.icsActive) {
11258         SendToICS(ics_prefix);
11259         SendToICS("adjourn\n");
11260     } else {
11261         /* Currently GNU Chess doesn't offer or accept Adjourns */
11262     }
11263 }
11264
11265
11266 void
11267 AbortEvent()
11268 {
11269     /* Offer Abort or accept pending Abort offer from opponent */
11270     
11271     if (appData.icsActive) {
11272         SendToICS(ics_prefix);
11273         SendToICS("abort\n");
11274     } else {
11275         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11276     }
11277 }
11278
11279 void
11280 ResignEvent()
11281 {
11282     /* Resign.  You can do this even if it's not your turn. */
11283     
11284     if (appData.icsActive) {
11285         SendToICS(ics_prefix);
11286         SendToICS("resign\n");
11287     } else {
11288         switch (gameMode) {
11289           case MachinePlaysWhite:
11290             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11291             break;
11292           case MachinePlaysBlack:
11293             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11294             break;
11295           case EditGame:
11296             if (cmailMsgLoaded) {
11297                 TruncateGame();
11298                 if (WhiteOnMove(cmailOldMove)) {
11299                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11300                 } else {
11301                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11302                 }
11303                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11304             }
11305             break;
11306           default:
11307             break;
11308         }
11309     }
11310 }
11311
11312
11313 void
11314 StopObservingEvent()
11315 {
11316     /* Stop observing current games */
11317     SendToICS(ics_prefix);
11318     SendToICS("unobserve\n");
11319 }
11320
11321 void
11322 StopExaminingEvent()
11323 {
11324     /* Stop observing current game */
11325     SendToICS(ics_prefix);
11326     SendToICS("unexamine\n");
11327 }
11328
11329 void
11330 ForwardInner(target)
11331      int target;
11332 {
11333     int limit;
11334
11335     if (appData.debugMode)
11336         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11337                 target, currentMove, forwardMostMove);
11338
11339     if (gameMode == EditPosition)
11340       return;
11341
11342     if (gameMode == PlayFromGameFile && !pausing)
11343       PauseEvent();
11344     
11345     if (gameMode == IcsExamining && pausing)
11346       limit = pauseExamForwardMostMove;
11347     else
11348       limit = forwardMostMove;
11349     
11350     if (target > limit) target = limit;
11351
11352     if (target > 0 && moveList[target - 1][0]) {
11353         int fromX, fromY, toX, toY;
11354         toX = moveList[target - 1][2] - AAA;
11355         toY = moveList[target - 1][3] - ONE;
11356         if (moveList[target - 1][1] == '@') {
11357             if (appData.highlightLastMove) {
11358                 SetHighlights(-1, -1, toX, toY);
11359             }
11360         } else {
11361             fromX = moveList[target - 1][0] - AAA;
11362             fromY = moveList[target - 1][1] - ONE;
11363             if (target == currentMove + 1) {
11364                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11365             }
11366             if (appData.highlightLastMove) {
11367                 SetHighlights(fromX, fromY, toX, toY);
11368             }
11369         }
11370     }
11371     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11372         gameMode == Training || gameMode == PlayFromGameFile || 
11373         gameMode == AnalyzeFile) {
11374         while (currentMove < target) {
11375             SendMoveToProgram(currentMove++, &first);
11376         }
11377     } else {
11378         currentMove = target;
11379     }
11380     
11381     if (gameMode == EditGame || gameMode == EndOfGame) {
11382         whiteTimeRemaining = timeRemaining[0][currentMove];
11383         blackTimeRemaining = timeRemaining[1][currentMove];
11384     }
11385     DisplayBothClocks();
11386     DisplayMove(currentMove - 1);
11387     DrawPosition(FALSE, boards[currentMove]);
11388     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11389     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11390         DisplayComment(currentMove - 1, commentList[currentMove]);
11391     }
11392 }
11393
11394
11395 void
11396 ForwardEvent()
11397 {
11398     if (gameMode == IcsExamining && !pausing) {
11399         SendToICS(ics_prefix);
11400         SendToICS("forward\n");
11401     } else {
11402         ForwardInner(currentMove + 1);
11403     }
11404 }
11405
11406 void
11407 ToEndEvent()
11408 {
11409     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11410         /* to optimze, we temporarily turn off analysis mode while we feed
11411          * the remaining moves to the engine. Otherwise we get analysis output
11412          * after each move.
11413          */ 
11414         if (first.analysisSupport) {
11415           SendToProgram("exit\nforce\n", &first);
11416           first.analyzing = FALSE;
11417         }
11418     }
11419         
11420     if (gameMode == IcsExamining && !pausing) {
11421         SendToICS(ics_prefix);
11422         SendToICS("forward 999999\n");
11423     } else {
11424         ForwardInner(forwardMostMove);
11425     }
11426
11427     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11428         /* we have fed all the moves, so reactivate analysis mode */
11429         SendToProgram("analyze\n", &first);
11430         first.analyzing = TRUE;
11431         /*first.maybeThinking = TRUE;*/
11432         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11433     }
11434 }
11435
11436 void
11437 BackwardInner(target)
11438      int target;
11439 {
11440     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11441
11442     if (appData.debugMode)
11443         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11444                 target, currentMove, forwardMostMove);
11445
11446     if (gameMode == EditPosition) return;
11447     if (currentMove <= backwardMostMove) {
11448         ClearHighlights();
11449         DrawPosition(full_redraw, boards[currentMove]);
11450         return;
11451     }
11452     if (gameMode == PlayFromGameFile && !pausing)
11453       PauseEvent();
11454     
11455     if (moveList[target][0]) {
11456         int fromX, fromY, toX, toY;
11457         toX = moveList[target][2] - AAA;
11458         toY = moveList[target][3] - ONE;
11459         if (moveList[target][1] == '@') {
11460             if (appData.highlightLastMove) {
11461                 SetHighlights(-1, -1, toX, toY);
11462             }
11463         } else {
11464             fromX = moveList[target][0] - AAA;
11465             fromY = moveList[target][1] - ONE;
11466             if (target == currentMove - 1) {
11467                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11468             }
11469             if (appData.highlightLastMove) {
11470                 SetHighlights(fromX, fromY, toX, toY);
11471             }
11472         }
11473     }
11474     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11475         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11476         while (currentMove > target) {
11477             SendToProgram("undo\n", &first);
11478             currentMove--;
11479         }
11480     } else {
11481         currentMove = target;
11482     }
11483     
11484     if (gameMode == EditGame || gameMode == EndOfGame) {
11485         whiteTimeRemaining = timeRemaining[0][currentMove];
11486         blackTimeRemaining = timeRemaining[1][currentMove];
11487     }
11488     DisplayBothClocks();
11489     DisplayMove(currentMove - 1);
11490     DrawPosition(full_redraw, boards[currentMove]);
11491     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11492     // [HGM] PV info: routine tests if comment empty
11493     DisplayComment(currentMove - 1, commentList[currentMove]);
11494 }
11495
11496 void
11497 BackwardEvent()
11498 {
11499     if (gameMode == IcsExamining && !pausing) {
11500         SendToICS(ics_prefix);
11501         SendToICS("backward\n");
11502     } else {
11503         BackwardInner(currentMove - 1);
11504     }
11505 }
11506
11507 void
11508 ToStartEvent()
11509 {
11510     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11511         /* to optimze, we temporarily turn off analysis mode while we undo
11512          * all the moves. Otherwise we get analysis output after each undo.
11513          */ 
11514         if (first.analysisSupport) {
11515           SendToProgram("exit\nforce\n", &first);
11516           first.analyzing = FALSE;
11517         }
11518     }
11519
11520     if (gameMode == IcsExamining && !pausing) {
11521         SendToICS(ics_prefix);
11522         SendToICS("backward 999999\n");
11523     } else {
11524         BackwardInner(backwardMostMove);
11525     }
11526
11527     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11528         /* we have fed all the moves, so reactivate analysis mode */
11529         SendToProgram("analyze\n", &first);
11530         first.analyzing = TRUE;
11531         /*first.maybeThinking = TRUE;*/
11532         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11533     }
11534 }
11535
11536 void
11537 ToNrEvent(int to)
11538 {
11539   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11540   if (to >= forwardMostMove) to = forwardMostMove;
11541   if (to <= backwardMostMove) to = backwardMostMove;
11542   if (to < currentMove) {
11543     BackwardInner(to);
11544   } else {
11545     ForwardInner(to);
11546   }
11547 }
11548
11549 void
11550 RevertEvent()
11551 {
11552     if (gameMode != IcsExamining) {
11553         DisplayError(_("You are not examining a game"), 0);
11554         return;
11555     }
11556     if (pausing) {
11557         DisplayError(_("You can't revert while pausing"), 0);
11558         return;
11559     }
11560     SendToICS(ics_prefix);
11561     SendToICS("revert\n");
11562 }
11563
11564 void
11565 RetractMoveEvent()
11566 {
11567     switch (gameMode) {
11568       case MachinePlaysWhite:
11569       case MachinePlaysBlack:
11570         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11571             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11572             return;
11573         }
11574         if (forwardMostMove < 2) return;
11575         currentMove = forwardMostMove = forwardMostMove - 2;
11576         whiteTimeRemaining = timeRemaining[0][currentMove];
11577         blackTimeRemaining = timeRemaining[1][currentMove];
11578         DisplayBothClocks();
11579         DisplayMove(currentMove - 1);
11580         ClearHighlights();/*!! could figure this out*/
11581         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11582         SendToProgram("remove\n", &first);
11583         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11584         break;
11585
11586       case BeginningOfGame:
11587       default:
11588         break;
11589
11590       case IcsPlayingWhite:
11591       case IcsPlayingBlack:
11592         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11593             SendToICS(ics_prefix);
11594             SendToICS("takeback 2\n");
11595         } else {
11596             SendToICS(ics_prefix);
11597             SendToICS("takeback 1\n");
11598         }
11599         break;
11600     }
11601 }
11602
11603 void
11604 MoveNowEvent()
11605 {
11606     ChessProgramState *cps;
11607
11608     switch (gameMode) {
11609       case MachinePlaysWhite:
11610         if (!WhiteOnMove(forwardMostMove)) {
11611             DisplayError(_("It is your turn"), 0);
11612             return;
11613         }
11614         cps = &first;
11615         break;
11616       case MachinePlaysBlack:
11617         if (WhiteOnMove(forwardMostMove)) {
11618             DisplayError(_("It is your turn"), 0);
11619             return;
11620         }
11621         cps = &first;
11622         break;
11623       case TwoMachinesPlay:
11624         if (WhiteOnMove(forwardMostMove) ==
11625             (first.twoMachinesColor[0] == 'w')) {
11626             cps = &first;
11627         } else {
11628             cps = &second;
11629         }
11630         break;
11631       case BeginningOfGame:
11632       default:
11633         return;
11634     }
11635     SendToProgram("?\n", cps);
11636 }
11637
11638 void
11639 TruncateGameEvent()
11640 {
11641     EditGameEvent();
11642     if (gameMode != EditGame) return;
11643     TruncateGame();
11644 }
11645
11646 void
11647 TruncateGame()
11648 {
11649     if (forwardMostMove > currentMove) {
11650         if (gameInfo.resultDetails != NULL) {
11651             free(gameInfo.resultDetails);
11652             gameInfo.resultDetails = NULL;
11653             gameInfo.result = GameUnfinished;
11654         }
11655         forwardMostMove = currentMove;
11656         HistorySet(parseList, backwardMostMove, forwardMostMove,
11657                    currentMove-1);
11658     }
11659 }
11660
11661 void
11662 HintEvent()
11663 {
11664     if (appData.noChessProgram) return;
11665     switch (gameMode) {
11666       case MachinePlaysWhite:
11667         if (WhiteOnMove(forwardMostMove)) {
11668             DisplayError(_("Wait until your turn"), 0);
11669             return;
11670         }
11671         break;
11672       case BeginningOfGame:
11673       case MachinePlaysBlack:
11674         if (!WhiteOnMove(forwardMostMove)) {
11675             DisplayError(_("Wait until your turn"), 0);
11676             return;
11677         }
11678         break;
11679       default:
11680         DisplayError(_("No hint available"), 0);
11681         return;
11682     }
11683     SendToProgram("hint\n", &first);
11684     hintRequested = TRUE;
11685 }
11686
11687 void
11688 BookEvent()
11689 {
11690     if (appData.noChessProgram) return;
11691     switch (gameMode) {
11692       case MachinePlaysWhite:
11693         if (WhiteOnMove(forwardMostMove)) {
11694             DisplayError(_("Wait until your turn"), 0);
11695             return;
11696         }
11697         break;
11698       case BeginningOfGame:
11699       case MachinePlaysBlack:
11700         if (!WhiteOnMove(forwardMostMove)) {
11701             DisplayError(_("Wait until your turn"), 0);
11702             return;
11703         }
11704         break;
11705       case EditPosition:
11706         EditPositionDone();
11707         break;
11708       case TwoMachinesPlay:
11709         return;
11710       default:
11711         break;
11712     }
11713     SendToProgram("bk\n", &first);
11714     bookOutput[0] = NULLCHAR;
11715     bookRequested = TRUE;
11716 }
11717
11718 void
11719 AboutGameEvent()
11720 {
11721     char *tags = PGNTags(&gameInfo);
11722     TagsPopUp(tags, CmailMsg());
11723     free(tags);
11724 }
11725
11726 /* end button procedures */
11727
11728 void
11729 PrintPosition(fp, move)
11730      FILE *fp;
11731      int move;
11732 {
11733     int i, j;
11734     
11735     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11736         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11737             char c = PieceToChar(boards[move][i][j]);
11738             fputc(c == 'x' ? '.' : c, fp);
11739             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11740         }
11741     }
11742     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11743       fprintf(fp, "white to play\n");
11744     else
11745       fprintf(fp, "black to play\n");
11746 }
11747
11748 void
11749 PrintOpponents(fp)
11750      FILE *fp;
11751 {
11752     if (gameInfo.white != NULL) {
11753         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11754     } else {
11755         fprintf(fp, "\n");
11756     }
11757 }
11758
11759 /* Find last component of program's own name, using some heuristics */
11760 void
11761 TidyProgramName(prog, host, buf)
11762      char *prog, *host, buf[MSG_SIZ];
11763 {
11764     char *p, *q;
11765     int local = (strcmp(host, "localhost") == 0);
11766     while (!local && (p = strchr(prog, ';')) != NULL) {
11767         p++;
11768         while (*p == ' ') p++;
11769         prog = p;
11770     }
11771     if (*prog == '"' || *prog == '\'') {
11772         q = strchr(prog + 1, *prog);
11773     } else {
11774         q = strchr(prog, ' ');
11775     }
11776     if (q == NULL) q = prog + strlen(prog);
11777     p = q;
11778     while (p >= prog && *p != '/' && *p != '\\') p--;
11779     p++;
11780     if(p == prog && *p == '"') p++;
11781     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11782     memcpy(buf, p, q - p);
11783     buf[q - p] = NULLCHAR;
11784     if (!local) {
11785         strcat(buf, "@");
11786         strcat(buf, host);
11787     }
11788 }
11789
11790 char *
11791 TimeControlTagValue()
11792 {
11793     char buf[MSG_SIZ];
11794     if (!appData.clockMode) {
11795         strcpy(buf, "-");
11796     } else if (movesPerSession > 0) {
11797         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11798     } else if (timeIncrement == 0) {
11799         sprintf(buf, "%ld", timeControl/1000);
11800     } else {
11801         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11802     }
11803     return StrSave(buf);
11804 }
11805
11806 void
11807 SetGameInfo()
11808 {
11809     /* This routine is used only for certain modes */
11810     VariantClass v = gameInfo.variant;
11811     ClearGameInfo(&gameInfo);
11812     gameInfo.variant = v;
11813
11814     switch (gameMode) {
11815       case MachinePlaysWhite:
11816         gameInfo.event = StrSave( appData.pgnEventHeader );
11817         gameInfo.site = StrSave(HostName());
11818         gameInfo.date = PGNDate();
11819         gameInfo.round = StrSave("-");
11820         gameInfo.white = StrSave(first.tidy);
11821         gameInfo.black = StrSave(UserName());
11822         gameInfo.timeControl = TimeControlTagValue();
11823         break;
11824
11825       case MachinePlaysBlack:
11826         gameInfo.event = StrSave( appData.pgnEventHeader );
11827         gameInfo.site = StrSave(HostName());
11828         gameInfo.date = PGNDate();
11829         gameInfo.round = StrSave("-");
11830         gameInfo.white = StrSave(UserName());
11831         gameInfo.black = StrSave(first.tidy);
11832         gameInfo.timeControl = TimeControlTagValue();
11833         break;
11834
11835       case TwoMachinesPlay:
11836         gameInfo.event = StrSave( appData.pgnEventHeader );
11837         gameInfo.site = StrSave(HostName());
11838         gameInfo.date = PGNDate();
11839         if (matchGame > 0) {
11840             char buf[MSG_SIZ];
11841             sprintf(buf, "%d", matchGame);
11842             gameInfo.round = StrSave(buf);
11843         } else {
11844             gameInfo.round = StrSave("-");
11845         }
11846         if (first.twoMachinesColor[0] == 'w') {
11847             gameInfo.white = StrSave(first.tidy);
11848             gameInfo.black = StrSave(second.tidy);
11849         } else {
11850             gameInfo.white = StrSave(second.tidy);
11851             gameInfo.black = StrSave(first.tidy);
11852         }
11853         gameInfo.timeControl = TimeControlTagValue();
11854         break;
11855
11856       case EditGame:
11857         gameInfo.event = StrSave("Edited game");
11858         gameInfo.site = StrSave(HostName());
11859         gameInfo.date = PGNDate();
11860         gameInfo.round = StrSave("-");
11861         gameInfo.white = StrSave("-");
11862         gameInfo.black = StrSave("-");
11863         break;
11864
11865       case EditPosition:
11866         gameInfo.event = StrSave("Edited position");
11867         gameInfo.site = StrSave(HostName());
11868         gameInfo.date = PGNDate();
11869         gameInfo.round = StrSave("-");
11870         gameInfo.white = StrSave("-");
11871         gameInfo.black = StrSave("-");
11872         break;
11873
11874       case IcsPlayingWhite:
11875       case IcsPlayingBlack:
11876       case IcsObserving:
11877       case IcsExamining:
11878         break;
11879
11880       case PlayFromGameFile:
11881         gameInfo.event = StrSave("Game from non-PGN file");
11882         gameInfo.site = StrSave(HostName());
11883         gameInfo.date = PGNDate();
11884         gameInfo.round = StrSave("-");
11885         gameInfo.white = StrSave("?");
11886         gameInfo.black = StrSave("?");
11887         break;
11888
11889       default:
11890         break;
11891     }
11892 }
11893
11894 void
11895 ReplaceComment(index, text)
11896      int index;
11897      char *text;
11898 {
11899     int len;
11900
11901     while (*text == '\n') text++;
11902     len = strlen(text);
11903     while (len > 0 && text[len - 1] == '\n') len--;
11904
11905     if (commentList[index] != NULL)
11906       free(commentList[index]);
11907
11908     if (len == 0) {
11909         commentList[index] = NULL;
11910         return;
11911     }
11912     commentList[index] = (char *) malloc(len + 2);
11913     strncpy(commentList[index], text, len);
11914     commentList[index][len] = '\n';
11915     commentList[index][len + 1] = NULLCHAR;
11916 }
11917
11918 void
11919 CrushCRs(text)
11920      char *text;
11921 {
11922   char *p = text;
11923   char *q = text;
11924   char ch;
11925
11926   do {
11927     ch = *p++;
11928     if (ch == '\r') continue;
11929     *q++ = ch;
11930   } while (ch != '\0');
11931 }
11932
11933 void
11934 AppendComment(index, text)
11935      int index;
11936      char *text;
11937 {
11938     int oldlen, len;
11939     char *old;
11940
11941     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
11942
11943     CrushCRs(text);
11944     while (*text == '\n') text++;
11945     len = strlen(text);
11946     while (len > 0 && text[len - 1] == '\n') len--;
11947
11948     if (len == 0) return;
11949
11950     if (commentList[index] != NULL) {
11951         old = commentList[index];
11952         oldlen = strlen(old);
11953         commentList[index] = (char *) malloc(oldlen + len + 2);
11954         strcpy(commentList[index], old);
11955         free(old);
11956         strncpy(&commentList[index][oldlen], text, len);
11957         commentList[index][oldlen + len] = '\n';
11958         commentList[index][oldlen + len + 1] = NULLCHAR;
11959     } else {
11960         commentList[index] = (char *) malloc(len + 2);
11961         strncpy(commentList[index], text, len);
11962         commentList[index][len] = '\n';
11963         commentList[index][len + 1] = NULLCHAR;
11964     }
11965 }
11966
11967 static char * FindStr( char * text, char * sub_text )
11968 {
11969     char * result = strstr( text, sub_text );
11970
11971     if( result != NULL ) {
11972         result += strlen( sub_text );
11973     }
11974
11975     return result;
11976 }
11977
11978 /* [AS] Try to extract PV info from PGN comment */
11979 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
11980 char *GetInfoFromComment( int index, char * text )
11981 {
11982     char * sep = text;
11983
11984     if( text != NULL && index > 0 ) {
11985         int score = 0;
11986         int depth = 0;
11987         int time = -1, sec = 0, deci;
11988         char * s_eval = FindStr( text, "[%eval " );
11989         char * s_emt = FindStr( text, "[%emt " );
11990
11991         if( s_eval != NULL || s_emt != NULL ) {
11992             /* New style */
11993             char delim;
11994
11995             if( s_eval != NULL ) {
11996                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
11997                     return text;
11998                 }
11999
12000                 if( delim != ']' ) {
12001                     return text;
12002                 }
12003             }
12004
12005             if( s_emt != NULL ) {
12006             }
12007         }
12008         else {
12009             /* We expect something like: [+|-]nnn.nn/dd */
12010             int score_lo = 0;
12011
12012             sep = strchr( text, '/' );
12013             if( sep == NULL || sep < (text+4) ) {
12014                 return text;
12015             }
12016
12017             time = -1; sec = -1; deci = -1;
12018             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12019                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12020                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12021                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12022                 return text;
12023             }
12024
12025             if( score_lo < 0 || score_lo >= 100 ) {
12026                 return text;
12027             }
12028
12029             if(sec >= 0) time = 600*time + 10*sec; else
12030             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12031
12032             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12033
12034             /* [HGM] PV time: now locate end of PV info */
12035             while( *++sep >= '0' && *sep <= '9'); // strip depth
12036             if(time >= 0)
12037             while( *++sep >= '0' && *sep <= '9'); // strip time
12038             if(sec >= 0)
12039             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12040             if(deci >= 0)
12041             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12042             while(*sep == ' ') sep++;
12043         }
12044
12045         if( depth <= 0 ) {
12046             return text;
12047         }
12048
12049         if( time < 0 ) {
12050             time = -1;
12051         }
12052
12053         pvInfoList[index-1].depth = depth;
12054         pvInfoList[index-1].score = score;
12055         pvInfoList[index-1].time  = 10*time; // centi-sec
12056     }
12057     return sep;
12058 }
12059
12060 void
12061 SendToProgram(message, cps)
12062      char *message;
12063      ChessProgramState *cps;
12064 {
12065     int count, outCount, error;
12066     char buf[MSG_SIZ];
12067
12068     if (cps->pr == NULL) return;
12069     Attention(cps);
12070     
12071     if (appData.debugMode) {
12072         TimeMark now;
12073         GetTimeMark(&now);
12074         fprintf(debugFP, "%ld >%-6s: %s", 
12075                 SubtractTimeMarks(&now, &programStartTime),
12076                 cps->which, message);
12077     }
12078     
12079     count = strlen(message);
12080     outCount = OutputToProcess(cps->pr, message, count, &error);
12081     if (outCount < count && !exiting 
12082                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12083         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12084         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12085             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12086                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12087                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12088             } else {
12089                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12090             }
12091             gameInfo.resultDetails = buf;
12092         }
12093         DisplayFatalError(buf, error, 1);
12094     }
12095 }
12096
12097 void
12098 ReceiveFromProgram(isr, closure, message, count, error)
12099      InputSourceRef isr;
12100      VOIDSTAR closure;
12101      char *message;
12102      int count;
12103      int error;
12104 {
12105     char *end_str;
12106     char buf[MSG_SIZ];
12107     ChessProgramState *cps = (ChessProgramState *)closure;
12108
12109     if (isr != cps->isr) return; /* Killed intentionally */
12110     if (count <= 0) {
12111         if (count == 0) {
12112             sprintf(buf,
12113                     _("Error: %s chess program (%s) exited unexpectedly"),
12114                     cps->which, cps->program);
12115         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12116                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12117                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12118                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12119                 } else {
12120                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12121                 }
12122                 gameInfo.resultDetails = buf;
12123             }
12124             RemoveInputSource(cps->isr);
12125             DisplayFatalError(buf, 0, 1);
12126         } else {
12127             sprintf(buf,
12128                     _("Error reading from %s chess program (%s)"),
12129                     cps->which, cps->program);
12130             RemoveInputSource(cps->isr);
12131
12132             /* [AS] Program is misbehaving badly... kill it */
12133             if( count == -2 ) {
12134                 DestroyChildProcess( cps->pr, 9 );
12135                 cps->pr = NoProc;
12136             }
12137
12138             DisplayFatalError(buf, error, 1);
12139         }
12140         return;
12141     }
12142     
12143     if ((end_str = strchr(message, '\r')) != NULL)
12144       *end_str = NULLCHAR;
12145     if ((end_str = strchr(message, '\n')) != NULL)
12146       *end_str = NULLCHAR;
12147     
12148     if (appData.debugMode) {
12149         TimeMark now; int print = 1;
12150         char *quote = ""; char c; int i;
12151
12152         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12153                 char start = message[0];
12154                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12155                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12156                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12157                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12158                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12159                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12160                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
12161                         { quote = "# "; print = (appData.engineComments == 2); }
12162                 message[0] = start; // restore original message
12163         }
12164         if(print) {
12165                 GetTimeMark(&now);
12166                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12167                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12168                         quote,
12169                         message);
12170         }
12171     }
12172
12173     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12174     if (appData.icsEngineAnalyze) {
12175         if (strstr(message, "whisper") != NULL ||
12176              strstr(message, "kibitz") != NULL || 
12177             strstr(message, "tellics") != NULL) return;
12178     }
12179
12180     HandleMachineMove(message, cps);
12181 }
12182
12183
12184 void
12185 SendTimeControl(cps, mps, tc, inc, sd, st)
12186      ChessProgramState *cps;
12187      int mps, inc, sd, st;
12188      long tc;
12189 {
12190     char buf[MSG_SIZ];
12191     int seconds;
12192
12193     if( timeControl_2 > 0 ) {
12194         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12195             tc = timeControl_2;
12196         }
12197     }
12198     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12199     inc /= cps->timeOdds;
12200     st  /= cps->timeOdds;
12201
12202     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12203
12204     if (st > 0) {
12205       /* Set exact time per move, normally using st command */
12206       if (cps->stKludge) {
12207         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12208         seconds = st % 60;
12209         if (seconds == 0) {
12210           sprintf(buf, "level 1 %d\n", st/60);
12211         } else {
12212           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12213         }
12214       } else {
12215         sprintf(buf, "st %d\n", st);
12216       }
12217     } else {
12218       /* Set conventional or incremental time control, using level command */
12219       if (seconds == 0) {
12220         /* Note old gnuchess bug -- minutes:seconds used to not work.
12221            Fixed in later versions, but still avoid :seconds
12222            when seconds is 0. */
12223         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12224       } else {
12225         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12226                 seconds, inc/1000);
12227       }
12228     }
12229     SendToProgram(buf, cps);
12230
12231     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12232     /* Orthogonally, limit search to given depth */
12233     if (sd > 0) {
12234       if (cps->sdKludge) {
12235         sprintf(buf, "depth\n%d\n", sd);
12236       } else {
12237         sprintf(buf, "sd %d\n", sd);
12238       }
12239       SendToProgram(buf, cps);
12240     }
12241
12242     if(cps->nps > 0) { /* [HGM] nps */
12243         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12244         else {
12245                 sprintf(buf, "nps %d\n", cps->nps);
12246               SendToProgram(buf, cps);
12247         }
12248     }
12249 }
12250
12251 ChessProgramState *WhitePlayer()
12252 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12253 {
12254     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12255        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12256         return &second;
12257     return &first;
12258 }
12259
12260 void
12261 SendTimeRemaining(cps, machineWhite)
12262      ChessProgramState *cps;
12263      int /*boolean*/ machineWhite;
12264 {
12265     char message[MSG_SIZ];
12266     long time, otime;
12267
12268     /* Note: this routine must be called when the clocks are stopped
12269        or when they have *just* been set or switched; otherwise
12270        it will be off by the time since the current tick started.
12271     */
12272     if (machineWhite) {
12273         time = whiteTimeRemaining / 10;
12274         otime = blackTimeRemaining / 10;
12275     } else {
12276         time = blackTimeRemaining / 10;
12277         otime = whiteTimeRemaining / 10;
12278     }
12279     /* [HGM] translate opponent's time by time-odds factor */
12280     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12281     if (appData.debugMode) {
12282         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12283     }
12284
12285     if (time <= 0) time = 1;
12286     if (otime <= 0) otime = 1;
12287     
12288     sprintf(message, "time %ld\n", time);
12289     SendToProgram(message, cps);
12290
12291     sprintf(message, "otim %ld\n", otime);
12292     SendToProgram(message, cps);
12293 }
12294
12295 int
12296 BoolFeature(p, name, loc, cps)
12297      char **p;
12298      char *name;
12299      int *loc;
12300      ChessProgramState *cps;
12301 {
12302   char buf[MSG_SIZ];
12303   int len = strlen(name);
12304   int val;
12305   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12306     (*p) += len + 1;
12307     sscanf(*p, "%d", &val);
12308     *loc = (val != 0);
12309     while (**p && **p != ' ') (*p)++;
12310     sprintf(buf, "accepted %s\n", name);
12311     SendToProgram(buf, cps);
12312     return TRUE;
12313   }
12314   return FALSE;
12315 }
12316
12317 int
12318 IntFeature(p, name, loc, cps)
12319      char **p;
12320      char *name;
12321      int *loc;
12322      ChessProgramState *cps;
12323 {
12324   char buf[MSG_SIZ];
12325   int len = strlen(name);
12326   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12327     (*p) += len + 1;
12328     sscanf(*p, "%d", loc);
12329     while (**p && **p != ' ') (*p)++;
12330     sprintf(buf, "accepted %s\n", name);
12331     SendToProgram(buf, cps);
12332     return TRUE;
12333   }
12334   return FALSE;
12335 }
12336
12337 int
12338 StringFeature(p, name, loc, cps)
12339      char **p;
12340      char *name;
12341      char loc[];
12342      ChessProgramState *cps;
12343 {
12344   char buf[MSG_SIZ];
12345   int len = strlen(name);
12346   if (strncmp((*p), name, len) == 0
12347       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12348     (*p) += len + 2;
12349     sscanf(*p, "%[^\"]", loc);
12350     while (**p && **p != '\"') (*p)++;
12351     if (**p == '\"') (*p)++;
12352     sprintf(buf, "accepted %s\n", name);
12353     SendToProgram(buf, cps);
12354     return TRUE;
12355   }
12356   return FALSE;
12357 }
12358
12359 int 
12360 ParseOption(Option *opt, ChessProgramState *cps)
12361 // [HGM] options: process the string that defines an engine option, and determine
12362 // name, type, default value, and allowed value range
12363 {
12364         char *p, *q, buf[MSG_SIZ];
12365         int n, min = (-1)<<31, max = 1<<31, def;
12366
12367         if(p = strstr(opt->name, " -spin ")) {
12368             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12369             if(max < min) max = min; // enforce consistency
12370             if(def < min) def = min;
12371             if(def > max) def = max;
12372             opt->value = def;
12373             opt->min = min;
12374             opt->max = max;
12375             opt->type = Spin;
12376         } else if(p = strstr(opt->name, " -string ")) {
12377             opt->textValue = p+9;
12378             opt->type = TextBox;
12379         } else if(p = strstr(opt->name, " -check ")) {
12380             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12381             opt->value = (def != 0);
12382             opt->type = CheckBox;
12383         } else if(p = strstr(opt->name, " -combo ")) {
12384             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12385             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12386             opt->value = n = 0;
12387             while(q = StrStr(q, " /// ")) {
12388                 n++; *q = 0;    // count choices, and null-terminate each of them
12389                 q += 5;
12390                 if(*q == '*') { // remember default, which is marked with * prefix
12391                     q++;
12392                     opt->value = n;
12393                 }
12394                 cps->comboList[cps->comboCnt++] = q;
12395             }
12396             cps->comboList[cps->comboCnt++] = NULL;
12397             opt->max = n + 1;
12398             opt->type = ComboBox;
12399         } else if(p = strstr(opt->name, " -button")) {
12400             opt->type = Button;
12401         } else if(p = strstr(opt->name, " -save")) {
12402             opt->type = SaveButton;
12403         } else return FALSE;
12404         *p = 0; // terminate option name
12405         // now look if the command-line options define a setting for this engine option.
12406         if(cps->optionSettings && cps->optionSettings[0])
12407             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12408         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12409                 sprintf(buf, "option %s", p);
12410                 if(p = strstr(buf, ",")) *p = 0;
12411                 strcat(buf, "\n");
12412                 SendToProgram(buf, cps);
12413         }
12414         return TRUE;
12415 }
12416
12417 void
12418 FeatureDone(cps, val)
12419      ChessProgramState* cps;
12420      int val;
12421 {
12422   DelayedEventCallback cb = GetDelayedEvent();
12423   if ((cb == InitBackEnd3 && cps == &first) ||
12424       (cb == TwoMachinesEventIfReady && cps == &second)) {
12425     CancelDelayedEvent();
12426     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12427   }
12428   cps->initDone = val;
12429 }
12430
12431 /* Parse feature command from engine */
12432 void
12433 ParseFeatures(args, cps)
12434      char* args;
12435      ChessProgramState *cps;  
12436 {
12437   char *p = args;
12438   char *q;
12439   int val;
12440   char buf[MSG_SIZ];
12441
12442   for (;;) {
12443     while (*p == ' ') p++;
12444     if (*p == NULLCHAR) return;
12445
12446     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12447     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12448     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12449     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12450     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12451     if (BoolFeature(&p, "reuse", &val, cps)) {
12452       /* Engine can disable reuse, but can't enable it if user said no */
12453       if (!val) cps->reuse = FALSE;
12454       continue;
12455     }
12456     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12457     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12458       if (gameMode == TwoMachinesPlay) {
12459         DisplayTwoMachinesTitle();
12460       } else {
12461         DisplayTitle("");
12462       }
12463       continue;
12464     }
12465     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12466     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12467     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12468     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12469     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12470     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12471     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12472     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12473     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12474     if (IntFeature(&p, "done", &val, cps)) {
12475       FeatureDone(cps, val);
12476       continue;
12477     }
12478     /* Added by Tord: */
12479     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12480     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12481     /* End of additions by Tord */
12482
12483     /* [HGM] added features: */
12484     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12485     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12486     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12487     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12488     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12489     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12490     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12491         ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature
12492         if(cps->nrOptions >= MAX_OPTIONS) {
12493             cps->nrOptions--;
12494             sprintf(buf, "%s engine has too many options\n", cps->which);
12495             DisplayError(buf, 0);
12496         }
12497         continue;
12498     }
12499     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12500     /* End of additions by HGM */
12501
12502     /* unknown feature: complain and skip */
12503     q = p;
12504     while (*q && *q != '=') q++;
12505     sprintf(buf, "rejected %.*s\n", q-p, p);
12506     SendToProgram(buf, cps);
12507     p = q;
12508     if (*p == '=') {
12509       p++;
12510       if (*p == '\"') {
12511         p++;
12512         while (*p && *p != '\"') p++;
12513         if (*p == '\"') p++;
12514       } else {
12515         while (*p && *p != ' ') p++;
12516       }
12517     }
12518   }
12519
12520 }
12521
12522 void
12523 PeriodicUpdatesEvent(newState)
12524      int newState;
12525 {
12526     if (newState == appData.periodicUpdates)
12527       return;
12528
12529     appData.periodicUpdates=newState;
12530
12531     /* Display type changes, so update it now */
12532     DisplayAnalysis();
12533
12534     /* Get the ball rolling again... */
12535     if (newState) {
12536         AnalysisPeriodicEvent(1);
12537         StartAnalysisClock();
12538     }
12539 }
12540
12541 void
12542 PonderNextMoveEvent(newState)
12543      int newState;
12544 {
12545     if (newState == appData.ponderNextMove) return;
12546     if (gameMode == EditPosition) EditPositionDone();
12547     if (newState) {
12548         SendToProgram("hard\n", &first);
12549         if (gameMode == TwoMachinesPlay) {
12550             SendToProgram("hard\n", &second);
12551         }
12552     } else {
12553         SendToProgram("easy\n", &first);
12554         thinkOutput[0] = NULLCHAR;
12555         if (gameMode == TwoMachinesPlay) {
12556             SendToProgram("easy\n", &second);
12557         }
12558     }
12559     appData.ponderNextMove = newState;
12560 }
12561
12562 void
12563 NewSettingEvent(option, command, value)
12564      char *command;
12565      int option, value;
12566 {
12567     char buf[MSG_SIZ];
12568
12569     if (gameMode == EditPosition) EditPositionDone();
12570     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12571     SendToProgram(buf, &first);
12572     if (gameMode == TwoMachinesPlay) {
12573         SendToProgram(buf, &second);
12574     }
12575 }
12576
12577 void
12578 ShowThinkingEvent()
12579 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12580 {
12581     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12582     int newState = appData.showThinking
12583         // [HGM] thinking: other features now need thinking output as well
12584         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12585     
12586     if (oldState == newState) return;
12587     oldState = newState;
12588     if (gameMode == EditPosition) EditPositionDone();
12589     if (oldState) {
12590         SendToProgram("post\n", &first);
12591         if (gameMode == TwoMachinesPlay) {
12592             SendToProgram("post\n", &second);
12593         }
12594     } else {
12595         SendToProgram("nopost\n", &first);
12596         thinkOutput[0] = NULLCHAR;
12597         if (gameMode == TwoMachinesPlay) {
12598             SendToProgram("nopost\n", &second);
12599         }
12600     }
12601 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12602 }
12603
12604 void
12605 AskQuestionEvent(title, question, replyPrefix, which)
12606      char *title; char *question; char *replyPrefix; char *which;
12607 {
12608   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12609   if (pr == NoProc) return;
12610   AskQuestion(title, question, replyPrefix, pr);
12611 }
12612
12613 void
12614 DisplayMove(moveNumber)
12615      int moveNumber;
12616 {
12617     char message[MSG_SIZ];
12618     char res[MSG_SIZ];
12619     char cpThinkOutput[MSG_SIZ];
12620
12621     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12622     
12623     if (moveNumber == forwardMostMove - 1 || 
12624         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12625
12626         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12627
12628         if (strchr(cpThinkOutput, '\n')) {
12629             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12630         }
12631     } else {
12632         *cpThinkOutput = NULLCHAR;
12633     }
12634
12635     /* [AS] Hide thinking from human user */
12636     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12637         *cpThinkOutput = NULLCHAR;
12638         if( thinkOutput[0] != NULLCHAR ) {
12639             int i;
12640
12641             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12642                 cpThinkOutput[i] = '.';
12643             }
12644             cpThinkOutput[i] = NULLCHAR;
12645             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12646         }
12647     }
12648
12649     if (moveNumber == forwardMostMove - 1 &&
12650         gameInfo.resultDetails != NULL) {
12651         if (gameInfo.resultDetails[0] == NULLCHAR) {
12652             sprintf(res, " %s", PGNResult(gameInfo.result));
12653         } else {
12654             sprintf(res, " {%s} %s",
12655                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12656         }
12657     } else {
12658         res[0] = NULLCHAR;
12659     }
12660
12661     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12662         DisplayMessage(res, cpThinkOutput);
12663     } else {
12664         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12665                 WhiteOnMove(moveNumber) ? " " : ".. ",
12666                 parseList[moveNumber], res);
12667         DisplayMessage(message, cpThinkOutput);
12668     }
12669 }
12670
12671 void
12672 DisplayAnalysisText(text)
12673      char *text;
12674 {
12675     char buf[MSG_SIZ];
12676
12677     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile 
12678                || appData.icsEngineAnalyze) {
12679         sprintf(buf, "Analysis (%s)", first.tidy);
12680         AnalysisPopUp(buf, text);
12681     }
12682 }
12683
12684 static int
12685 only_one_move(str)
12686      char *str;
12687 {
12688     while (*str && isspace(*str)) ++str;
12689     while (*str && !isspace(*str)) ++str;
12690     if (!*str) return 1;
12691     while (*str && isspace(*str)) ++str;
12692     if (!*str) return 1;
12693     return 0;
12694 }
12695
12696 void
12697 DisplayAnalysis()
12698 {
12699     char buf[MSG_SIZ];
12700     char lst[MSG_SIZ / 2];
12701     double nps;
12702     static char *xtra[] = { "", " (--)", " (++)" };
12703     int h, m, s, cs;
12704   
12705     if (programStats.time == 0) {
12706         programStats.time = 1;
12707     }
12708   
12709     if (programStats.got_only_move) {
12710         safeStrCpy(buf, programStats.movelist, sizeof(buf));
12711     } else {
12712         safeStrCpy( lst, programStats.movelist, sizeof(lst));
12713
12714         nps = (u64ToDouble(programStats.nodes) /
12715              ((double)programStats.time /100.0));
12716
12717         cs = programStats.time % 100;
12718         s = programStats.time / 100;
12719         h = (s / (60*60));
12720         s = s - h*60*60;
12721         m = (s/60);
12722         s = s - m*60;
12723
12724         if (programStats.moves_left > 0 && appData.periodicUpdates) {
12725           if (programStats.move_name[0] != NULLCHAR) {
12726             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12727                     programStats.depth,
12728                     programStats.nr_moves-programStats.moves_left,
12729                     programStats.nr_moves, programStats.move_name,
12730                     ((float)programStats.score)/100.0, lst,
12731                     only_one_move(lst)?
12732                     xtra[programStats.got_fail] : "",
12733                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12734           } else {
12735             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12736                     programStats.depth,
12737                     programStats.nr_moves-programStats.moves_left,
12738                     programStats.nr_moves, ((float)programStats.score)/100.0,
12739                     lst,
12740                     only_one_move(lst)?
12741                     xtra[programStats.got_fail] : "",
12742                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12743           }
12744         } else {
12745             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12746                     programStats.depth,
12747                     ((float)programStats.score)/100.0,
12748                     lst,
12749                     only_one_move(lst)?
12750                     xtra[programStats.got_fail] : "",
12751                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12752         }
12753     }
12754     DisplayAnalysisText(buf);
12755 }
12756
12757 void
12758 DisplayComment(moveNumber, text)
12759      int moveNumber;
12760      char *text;
12761 {
12762     char title[MSG_SIZ];
12763     char buf[8000]; // comment can be long!
12764     int score, depth;
12765
12766     if( appData.autoDisplayComment ) {
12767         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12768             strcpy(title, "Comment");
12769         } else {
12770             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12771                     WhiteOnMove(moveNumber) ? " " : ".. ",
12772                     parseList[moveNumber]);
12773         }
12774         // [HGM] PV info: display PV info together with (or as) comment
12775         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12776             if(text == NULL) text = "";                                           
12777             score = pvInfoList[moveNumber].score;
12778             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12779                               depth, (pvInfoList[moveNumber].time+50)/100, text);
12780             text = buf;
12781         }
12782     } else title[0] = 0;
12783
12784     if (text != NULL)
12785         CommentPopUp(title, text);
12786 }
12787
12788 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12789  * might be busy thinking or pondering.  It can be omitted if your
12790  * gnuchess is configured to stop thinking immediately on any user
12791  * input.  However, that gnuchess feature depends on the FIONREAD
12792  * ioctl, which does not work properly on some flavors of Unix.
12793  */
12794 void
12795 Attention(cps)
12796      ChessProgramState *cps;
12797 {
12798 #if ATTENTION
12799     if (!cps->useSigint) return;
12800     if (appData.noChessProgram || (cps->pr == NoProc)) return;
12801     switch (gameMode) {
12802       case MachinePlaysWhite:
12803       case MachinePlaysBlack:
12804       case TwoMachinesPlay:
12805       case IcsPlayingWhite:
12806       case IcsPlayingBlack:
12807       case AnalyzeMode:
12808       case AnalyzeFile:
12809         /* Skip if we know it isn't thinking */
12810         if (!cps->maybeThinking) return;
12811         if (appData.debugMode)
12812           fprintf(debugFP, "Interrupting %s\n", cps->which);
12813         InterruptChildProcess(cps->pr);
12814         cps->maybeThinking = FALSE;
12815         break;
12816       default:
12817         break;
12818     }
12819 #endif /*ATTENTION*/
12820 }
12821
12822 int
12823 CheckFlags()
12824 {
12825     if (whiteTimeRemaining <= 0) {
12826         if (!whiteFlag) {
12827             whiteFlag = TRUE;
12828             if (appData.icsActive) {
12829                 if (appData.autoCallFlag &&
12830                     gameMode == IcsPlayingBlack && !blackFlag) {
12831                   SendToICS(ics_prefix);
12832                   SendToICS("flag\n");
12833                 }
12834             } else {
12835                 if (blackFlag) {
12836                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12837                 } else {
12838                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12839                     if (appData.autoCallFlag) {
12840                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12841                         return TRUE;
12842                     }
12843                 }
12844             }
12845         }
12846     }
12847     if (blackTimeRemaining <= 0) {
12848         if (!blackFlag) {
12849             blackFlag = TRUE;
12850             if (appData.icsActive) {
12851                 if (appData.autoCallFlag &&
12852                     gameMode == IcsPlayingWhite && !whiteFlag) {
12853                   SendToICS(ics_prefix);
12854                   SendToICS("flag\n");
12855                 }
12856             } else {
12857                 if (whiteFlag) {
12858                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12859                 } else {
12860                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12861                     if (appData.autoCallFlag) {
12862                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12863                         return TRUE;
12864                     }
12865                 }
12866             }
12867         }
12868     }
12869     return FALSE;
12870 }
12871
12872 void
12873 CheckTimeControl()
12874 {
12875     if (!appData.clockMode || appData.icsActive ||
12876         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12877
12878     /*
12879      * add time to clocks when time control is achieved ([HGM] now also used for increment)
12880      */
12881     if ( !WhiteOnMove(forwardMostMove) )
12882         /* White made time control */
12883         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12884         /* [HGM] time odds: correct new time quota for time odds! */
12885                                             / WhitePlayer()->timeOdds;
12886       else
12887         /* Black made time control */
12888         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12889                                             / WhitePlayer()->other->timeOdds;
12890 }
12891
12892 void
12893 DisplayBothClocks()
12894 {
12895     int wom = gameMode == EditPosition ?
12896       !blackPlaysFirst : WhiteOnMove(currentMove);
12897     DisplayWhiteClock(whiteTimeRemaining, wom);
12898     DisplayBlackClock(blackTimeRemaining, !wom);
12899 }
12900
12901
12902 /* Timekeeping seems to be a portability nightmare.  I think everyone
12903    has ftime(), but I'm really not sure, so I'm including some ifdefs
12904    to use other calls if you don't.  Clocks will be less accurate if
12905    you have neither ftime nor gettimeofday.
12906 */
12907
12908 /* VS 2008 requires the #include outside of the function */
12909 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
12910 #include <sys/timeb.h>
12911 #endif
12912
12913 /* Get the current time as a TimeMark */
12914 void
12915 GetTimeMark(tm)
12916      TimeMark *tm;
12917 {
12918 #if HAVE_GETTIMEOFDAY
12919
12920     struct timeval timeVal;
12921     struct timezone timeZone;
12922
12923     gettimeofday(&timeVal, &timeZone);
12924     tm->sec = (long) timeVal.tv_sec; 
12925     tm->ms = (int) (timeVal.tv_usec / 1000L);
12926
12927 #else /*!HAVE_GETTIMEOFDAY*/
12928 #if HAVE_FTIME
12929
12930 // include <sys/timeb.h> / moved to just above start of function
12931     struct timeb timeB;
12932
12933     ftime(&timeB);
12934     tm->sec = (long) timeB.time;
12935     tm->ms = (int) timeB.millitm;
12936
12937 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
12938     tm->sec = (long) time(NULL);
12939     tm->ms = 0;
12940 #endif
12941 #endif
12942 }
12943
12944 /* Return the difference in milliseconds between two
12945    time marks.  We assume the difference will fit in a long!
12946 */
12947 long
12948 SubtractTimeMarks(tm2, tm1)
12949      TimeMark *tm2, *tm1;
12950 {
12951     return 1000L*(tm2->sec - tm1->sec) +
12952            (long) (tm2->ms - tm1->ms);
12953 }
12954
12955
12956 /*
12957  * Code to manage the game clocks.
12958  *
12959  * In tournament play, black starts the clock and then white makes a move.
12960  * We give the human user a slight advantage if he is playing white---the
12961  * clocks don't run until he makes his first move, so it takes zero time.
12962  * Also, we don't account for network lag, so we could get out of sync
12963  * with GNU Chess's clock -- but then, referees are always right.  
12964  */
12965
12966 static TimeMark tickStartTM;
12967 static long intendedTickLength;
12968
12969 long
12970 NextTickLength(timeRemaining)
12971      long timeRemaining;
12972 {
12973     long nominalTickLength, nextTickLength;
12974
12975     if (timeRemaining > 0L && timeRemaining <= 10000L)
12976       nominalTickLength = 100L;
12977     else
12978       nominalTickLength = 1000L;
12979     nextTickLength = timeRemaining % nominalTickLength;
12980     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
12981
12982     return nextTickLength;
12983 }
12984
12985 /* Adjust clock one minute up or down */
12986 void
12987 AdjustClock(Boolean which, int dir)
12988 {
12989     if(which) blackTimeRemaining += 60000*dir;
12990     else      whiteTimeRemaining += 60000*dir;
12991     DisplayBothClocks();
12992 }
12993
12994 /* Stop clocks and reset to a fresh time control */
12995 void
12996 ResetClocks() 
12997 {
12998     (void) StopClockTimer();
12999     if (appData.icsActive) {
13000         whiteTimeRemaining = blackTimeRemaining = 0;
13001     } else { /* [HGM] correct new time quote for time odds */
13002         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13003         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13004     }
13005     if (whiteFlag || blackFlag) {
13006         DisplayTitle("");
13007         whiteFlag = blackFlag = FALSE;
13008     }
13009     DisplayBothClocks();
13010 }
13011
13012 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13013
13014 /* Decrement running clock by amount of time that has passed */
13015 void
13016 DecrementClocks()
13017 {
13018     long timeRemaining;
13019     long lastTickLength, fudge;
13020     TimeMark now;
13021
13022     if (!appData.clockMode) return;
13023     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13024         
13025     GetTimeMark(&now);
13026
13027     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13028
13029     /* Fudge if we woke up a little too soon */
13030     fudge = intendedTickLength - lastTickLength;
13031     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13032
13033     if (WhiteOnMove(forwardMostMove)) {
13034         if(whiteNPS >= 0) lastTickLength = 0;
13035         timeRemaining = whiteTimeRemaining -= lastTickLength;
13036         DisplayWhiteClock(whiteTimeRemaining - fudge,
13037                           WhiteOnMove(currentMove));
13038     } else {
13039         if(blackNPS >= 0) lastTickLength = 0;
13040         timeRemaining = blackTimeRemaining -= lastTickLength;
13041         DisplayBlackClock(blackTimeRemaining - fudge,
13042                           !WhiteOnMove(currentMove));
13043     }
13044
13045     if (CheckFlags()) return;
13046         
13047     tickStartTM = now;
13048     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13049     StartClockTimer(intendedTickLength);
13050
13051     /* if the time remaining has fallen below the alarm threshold, sound the
13052      * alarm. if the alarm has sounded and (due to a takeback or time control
13053      * with increment) the time remaining has increased to a level above the
13054      * threshold, reset the alarm so it can sound again. 
13055      */
13056     
13057     if (appData.icsActive && appData.icsAlarm) {
13058
13059         /* make sure we are dealing with the user's clock */
13060         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13061                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13062            )) return;
13063
13064         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13065             alarmSounded = FALSE;
13066         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13067             PlayAlarmSound();
13068             alarmSounded = TRUE;
13069         }
13070     }
13071 }
13072
13073
13074 /* A player has just moved, so stop the previously running
13075    clock and (if in clock mode) start the other one.
13076    We redisplay both clocks in case we're in ICS mode, because
13077    ICS gives us an update to both clocks after every move.
13078    Note that this routine is called *after* forwardMostMove
13079    is updated, so the last fractional tick must be subtracted
13080    from the color that is *not* on move now.
13081 */
13082 void
13083 SwitchClocks()
13084 {
13085     long lastTickLength;
13086     TimeMark now;
13087     int flagged = FALSE;
13088
13089     GetTimeMark(&now);
13090
13091     if (StopClockTimer() && appData.clockMode) {
13092         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13093         if (WhiteOnMove(forwardMostMove)) {
13094             if(blackNPS >= 0) lastTickLength = 0;
13095             blackTimeRemaining -= lastTickLength;
13096            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13097 //         if(pvInfoList[forwardMostMove-1].time == -1)
13098                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13099                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13100         } else {
13101            if(whiteNPS >= 0) lastTickLength = 0;
13102            whiteTimeRemaining -= lastTickLength;
13103            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13104 //         if(pvInfoList[forwardMostMove-1].time == -1)
13105                  pvInfoList[forwardMostMove-1].time = 
13106                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13107         }
13108         flagged = CheckFlags();
13109     }
13110     CheckTimeControl();
13111
13112     if (flagged || !appData.clockMode) return;
13113
13114     switch (gameMode) {
13115       case MachinePlaysBlack:
13116       case MachinePlaysWhite:
13117       case BeginningOfGame:
13118         if (pausing) return;
13119         break;
13120
13121       case EditGame:
13122       case PlayFromGameFile:
13123       case IcsExamining:
13124         return;
13125
13126       default:
13127         break;
13128     }
13129
13130     tickStartTM = now;
13131     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13132       whiteTimeRemaining : blackTimeRemaining);
13133     StartClockTimer(intendedTickLength);
13134 }
13135         
13136
13137 /* Stop both clocks */
13138 void
13139 StopClocks()
13140 {       
13141     long lastTickLength;
13142     TimeMark now;
13143
13144     if (!StopClockTimer()) return;
13145     if (!appData.clockMode) return;
13146
13147     GetTimeMark(&now);
13148
13149     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13150     if (WhiteOnMove(forwardMostMove)) {
13151         if(whiteNPS >= 0) lastTickLength = 0;
13152         whiteTimeRemaining -= lastTickLength;
13153         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13154     } else {
13155         if(blackNPS >= 0) lastTickLength = 0;
13156         blackTimeRemaining -= lastTickLength;
13157         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13158     }
13159     CheckFlags();
13160 }
13161         
13162 /* Start clock of player on move.  Time may have been reset, so
13163    if clock is already running, stop and restart it. */
13164 void
13165 StartClocks()
13166 {
13167     (void) StopClockTimer(); /* in case it was running already */
13168     DisplayBothClocks();
13169     if (CheckFlags()) return;
13170
13171     if (!appData.clockMode) return;
13172     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13173
13174     GetTimeMark(&tickStartTM);
13175     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13176       whiteTimeRemaining : blackTimeRemaining);
13177
13178    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13179     whiteNPS = blackNPS = -1; 
13180     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13181        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13182         whiteNPS = first.nps;
13183     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13184        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13185         blackNPS = first.nps;
13186     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13187         whiteNPS = second.nps;
13188     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13189         blackNPS = second.nps;
13190     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13191
13192     StartClockTimer(intendedTickLength);
13193 }
13194
13195 char *
13196 TimeString(ms)
13197      long ms;
13198 {
13199     long second, minute, hour, day;
13200     char *sign = "";
13201     static char buf[32];
13202     
13203     if (ms > 0 && ms <= 9900) {
13204       /* convert milliseconds to tenths, rounding up */
13205       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13206
13207       sprintf(buf, " %03.1f ", tenths/10.0);
13208       return buf;
13209     }
13210
13211     /* convert milliseconds to seconds, rounding up */
13212     /* use floating point to avoid strangeness of integer division
13213        with negative dividends on many machines */
13214     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13215
13216     if (second < 0) {
13217         sign = "-";
13218         second = -second;
13219     }
13220     
13221     day = second / (60 * 60 * 24);
13222     second = second % (60 * 60 * 24);
13223     hour = second / (60 * 60);
13224     second = second % (60 * 60);
13225     minute = second / 60;
13226     second = second % 60;
13227     
13228     if (day > 0)
13229       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13230               sign, day, hour, minute, second);
13231     else if (hour > 0)
13232       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13233     else
13234       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13235     
13236     return buf;
13237 }
13238
13239
13240 /*
13241  * This is necessary because some C libraries aren't ANSI C compliant yet.
13242  */
13243 char *
13244 StrStr(string, match)
13245      char *string, *match;
13246 {
13247     int i, length;
13248     
13249     length = strlen(match);
13250     
13251     for (i = strlen(string) - length; i >= 0; i--, string++)
13252       if (!strncmp(match, string, length))
13253         return string;
13254     
13255     return NULL;
13256 }
13257
13258 char *
13259 StrCaseStr(string, match)
13260      char *string, *match;
13261 {
13262     int i, j, length;
13263     
13264     length = strlen(match);
13265     
13266     for (i = strlen(string) - length; i >= 0; i--, string++) {
13267         for (j = 0; j < length; j++) {
13268             if (ToLower(match[j]) != ToLower(string[j]))
13269               break;
13270         }
13271         if (j == length) return string;
13272     }
13273
13274     return NULL;
13275 }
13276
13277 #ifndef _amigados
13278 int
13279 StrCaseCmp(s1, s2)
13280      char *s1, *s2;
13281 {
13282     char c1, c2;
13283     
13284     for (;;) {
13285         c1 = ToLower(*s1++);
13286         c2 = ToLower(*s2++);
13287         if (c1 > c2) return 1;
13288         if (c1 < c2) return -1;
13289         if (c1 == NULLCHAR) return 0;
13290     }
13291 }
13292
13293
13294 int
13295 ToLower(c)
13296      int c;
13297 {
13298     return isupper(c) ? tolower(c) : c;
13299 }
13300
13301
13302 int
13303 ToUpper(c)
13304      int c;
13305 {
13306     return islower(c) ? toupper(c) : c;
13307 }
13308 #endif /* !_amigados    */
13309
13310 char *
13311 StrSave(s)
13312      char *s;
13313 {
13314     char *ret;
13315
13316     if ((ret = (char *) malloc(strlen(s) + 1))) {
13317         strcpy(ret, s);
13318     }
13319     return ret;
13320 }
13321
13322 char *
13323 StrSavePtr(s, savePtr)
13324      char *s, **savePtr;
13325 {
13326     if (*savePtr) {
13327         free(*savePtr);
13328     }
13329     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13330         strcpy(*savePtr, s);
13331     }
13332     return(*savePtr);
13333 }
13334
13335 char *
13336 PGNDate()
13337 {
13338     time_t clock;
13339     struct tm *tm;
13340     char buf[MSG_SIZ];
13341
13342     clock = time((time_t *)NULL);
13343     tm = localtime(&clock);
13344     sprintf(buf, "%04d.%02d.%02d",
13345             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13346     return StrSave(buf);
13347 }
13348
13349
13350 char *
13351 PositionToFEN(move, overrideCastling)
13352      int move;
13353      char *overrideCastling;
13354 {
13355     int i, j, fromX, fromY, toX, toY;
13356     int whiteToPlay;
13357     char buf[128];
13358     char *p, *q;
13359     int emptycount;
13360     ChessSquare piece;
13361
13362     whiteToPlay = (gameMode == EditPosition) ?
13363       !blackPlaysFirst : (move % 2 == 0);
13364     p = buf;
13365
13366     /* Piece placement data */
13367     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13368         emptycount = 0;
13369         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13370             if (boards[move][i][j] == EmptySquare) {
13371                 emptycount++;
13372             } else { ChessSquare piece = boards[move][i][j];
13373                 if (emptycount > 0) {
13374                     if(emptycount<10) /* [HGM] can be >= 10 */
13375                         *p++ = '0' + emptycount;
13376                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13377                     emptycount = 0;
13378                 }
13379                 if(PieceToChar(piece) == '+') {
13380                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13381                     *p++ = '+';
13382                     piece = (ChessSquare)(DEMOTED piece);
13383                 } 
13384                 *p++ = PieceToChar(piece);
13385                 if(p[-1] == '~') {
13386                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13387                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13388                     *p++ = '~';
13389                 }
13390             }
13391         }
13392         if (emptycount > 0) {
13393             if(emptycount<10) /* [HGM] can be >= 10 */
13394                 *p++ = '0' + emptycount;
13395             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13396             emptycount = 0;
13397         }
13398         *p++ = '/';
13399     }
13400     *(p - 1) = ' ';
13401
13402     /* [HGM] print Crazyhouse or Shogi holdings */
13403     if( gameInfo.holdingsWidth ) {
13404         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13405         q = p;
13406         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13407             piece = boards[move][i][BOARD_WIDTH-1];
13408             if( piece != EmptySquare )
13409               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13410                   *p++ = PieceToChar(piece);
13411         }
13412         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13413             piece = boards[move][BOARD_HEIGHT-i-1][0];
13414             if( piece != EmptySquare )
13415               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13416                   *p++ = PieceToChar(piece);
13417         }
13418
13419         if( q == p ) *p++ = '-';
13420         *p++ = ']';
13421         *p++ = ' ';
13422     }
13423
13424     /* Active color */
13425     *p++ = whiteToPlay ? 'w' : 'b';
13426     *p++ = ' ';
13427
13428   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13429     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13430   } else {
13431   if(nrCastlingRights) {
13432      q = p;
13433      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13434        /* [HGM] write directly from rights */
13435            if(castlingRights[move][2] >= 0 &&
13436               castlingRights[move][0] >= 0   )
13437                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13438            if(castlingRights[move][2] >= 0 &&
13439               castlingRights[move][1] >= 0   )
13440                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13441            if(castlingRights[move][5] >= 0 &&
13442               castlingRights[move][3] >= 0   )
13443                 *p++ = castlingRights[move][3] + AAA;
13444            if(castlingRights[move][5] >= 0 &&
13445               castlingRights[move][4] >= 0   )
13446                 *p++ = castlingRights[move][4] + AAA;
13447      } else {
13448
13449         /* [HGM] write true castling rights */
13450         if( nrCastlingRights == 6 ) {
13451             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13452                castlingRights[move][2] >= 0  ) *p++ = 'K';
13453             if(castlingRights[move][1] == BOARD_LEFT &&
13454                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13455             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13456                castlingRights[move][5] >= 0  ) *p++ = 'k';
13457             if(castlingRights[move][4] == BOARD_LEFT &&
13458                castlingRights[move][5] >= 0  ) *p++ = 'q';
13459         }
13460      }
13461      if (q == p) *p++ = '-'; /* No castling rights */
13462      *p++ = ' ';
13463   }
13464
13465   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13466      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13467     /* En passant target square */
13468     if (move > backwardMostMove) {
13469         fromX = moveList[move - 1][0] - AAA;
13470         fromY = moveList[move - 1][1] - ONE;
13471         toX = moveList[move - 1][2] - AAA;
13472         toY = moveList[move - 1][3] - ONE;
13473         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13474             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13475             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13476             fromX == toX) {
13477             /* 2-square pawn move just happened */
13478             *p++ = toX + AAA;
13479             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13480         } else {
13481             *p++ = '-';
13482         }
13483     } else {
13484         *p++ = '-';
13485     }
13486     *p++ = ' ';
13487   }
13488   }
13489
13490     /* [HGM] find reversible plies */
13491     {   int i = 0, j=move;
13492
13493         if (appData.debugMode) { int k;
13494             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13495             for(k=backwardMostMove; k<=forwardMostMove; k++)
13496                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13497
13498         }
13499
13500         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13501         if( j == backwardMostMove ) i += initialRulePlies;
13502         sprintf(p, "%d ", i);
13503         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13504     }
13505     /* Fullmove number */
13506     sprintf(p, "%d", (move / 2) + 1);
13507     
13508     return StrSave(buf);
13509 }
13510
13511 Boolean
13512 ParseFEN(board, blackPlaysFirst, fen)
13513     Board board;
13514      int *blackPlaysFirst;
13515      char *fen;
13516 {
13517     int i, j;
13518     char *p;
13519     int emptycount;
13520     ChessSquare piece;
13521
13522     p = fen;
13523
13524     /* [HGM] by default clear Crazyhouse holdings, if present */
13525     if(gameInfo.holdingsWidth) {
13526        for(i=0; i<BOARD_HEIGHT; i++) {
13527            board[i][0]             = EmptySquare; /* black holdings */
13528            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13529            board[i][1]             = (ChessSquare) 0; /* black counts */
13530            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13531        }
13532     }
13533
13534     /* Piece placement data */
13535     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13536         j = 0;
13537         for (;;) {
13538             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13539                 if (*p == '/') p++;
13540                 emptycount = gameInfo.boardWidth - j;
13541                 while (emptycount--)
13542                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13543                 break;
13544 #if(BOARD_SIZE >= 10)
13545             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13546                 p++; emptycount=10;
13547                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13548                 while (emptycount--)
13549                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13550 #endif
13551             } else if (isdigit(*p)) {
13552                 emptycount = *p++ - '0';
13553                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13554                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13555                 while (emptycount--)
13556                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13557             } else if (*p == '+' || isalpha(*p)) {
13558                 if (j >= gameInfo.boardWidth) return FALSE;
13559                 if(*p=='+') {
13560                     piece = CharToPiece(*++p);
13561                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13562                     piece = (ChessSquare) (PROMOTED piece ); p++;
13563                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13564                 } else piece = CharToPiece(*p++);
13565
13566                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13567                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13568                     piece = (ChessSquare) (PROMOTED piece);
13569                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13570                     p++;
13571                 }
13572                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13573             } else {
13574                 return FALSE;
13575             }
13576         }
13577     }
13578     while (*p == '/' || *p == ' ') p++;
13579
13580     /* [HGM] look for Crazyhouse holdings here */
13581     while(*p==' ') p++;
13582     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13583         if(*p == '[') p++;
13584         if(*p == '-' ) *p++; /* empty holdings */ else {
13585             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13586             /* if we would allow FEN reading to set board size, we would   */
13587             /* have to add holdings and shift the board read so far here   */
13588             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13589                 *p++;
13590                 if((int) piece >= (int) BlackPawn ) {
13591                     i = (int)piece - (int)BlackPawn;
13592                     i = PieceToNumber((ChessSquare)i);
13593                     if( i >= gameInfo.holdingsSize ) return FALSE;
13594                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13595                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13596                 } else {
13597                     i = (int)piece - (int)WhitePawn;
13598                     i = PieceToNumber((ChessSquare)i);
13599                     if( i >= gameInfo.holdingsSize ) return FALSE;
13600                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13601                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13602                 }
13603             }
13604         }
13605         if(*p == ']') *p++;
13606     }
13607
13608     while(*p == ' ') p++;
13609
13610     /* Active color */
13611     switch (*p++) {
13612       case 'w':
13613         *blackPlaysFirst = FALSE;
13614         break;
13615       case 'b': 
13616         *blackPlaysFirst = TRUE;
13617         break;
13618       default:
13619         return FALSE;
13620     }
13621
13622     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13623     /* return the extra info in global variiables             */
13624
13625     /* set defaults in case FEN is incomplete */
13626     FENepStatus = EP_UNKNOWN;
13627     for(i=0; i<nrCastlingRights; i++ ) {
13628         FENcastlingRights[i] =
13629             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13630     }   /* assume possible unless obviously impossible */
13631     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13632     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13633     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13634     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13635     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13636     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13637     FENrulePlies = 0;
13638
13639     while(*p==' ') p++;
13640     if(nrCastlingRights) {
13641       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13642           /* castling indicator present, so default becomes no castlings */
13643           for(i=0; i<nrCastlingRights; i++ ) {
13644                  FENcastlingRights[i] = -1;
13645           }
13646       }
13647       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13648              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13649              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13650              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13651         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13652
13653         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13654             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13655             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13656         }
13657         switch(c) {
13658           case'K':
13659               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13660               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13661               FENcastlingRights[2] = whiteKingFile;
13662               break;
13663           case'Q':
13664               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13665               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13666               FENcastlingRights[2] = whiteKingFile;
13667               break;
13668           case'k':
13669               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13670               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13671               FENcastlingRights[5] = blackKingFile;
13672               break;
13673           case'q':
13674               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13675               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13676               FENcastlingRights[5] = blackKingFile;
13677           case '-':
13678               break;
13679           default: /* FRC castlings */
13680               if(c >= 'a') { /* black rights */
13681                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13682                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13683                   if(i == BOARD_RGHT) break;
13684                   FENcastlingRights[5] = i;
13685                   c -= AAA;
13686                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13687                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13688                   if(c > i)
13689                       FENcastlingRights[3] = c;
13690                   else
13691                       FENcastlingRights[4] = c;
13692               } else { /* white rights */
13693                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13694                     if(board[0][i] == WhiteKing) break;
13695                   if(i == BOARD_RGHT) break;
13696                   FENcastlingRights[2] = i;
13697                   c -= AAA - 'a' + 'A';
13698                   if(board[0][c] >= WhiteKing) break;
13699                   if(c > i)
13700                       FENcastlingRights[0] = c;
13701                   else
13702                       FENcastlingRights[1] = c;
13703               }
13704         }
13705       }
13706     if (appData.debugMode) {
13707         fprintf(debugFP, "FEN castling rights:");
13708         for(i=0; i<nrCastlingRights; i++)
13709         fprintf(debugFP, " %d", FENcastlingRights[i]);
13710         fprintf(debugFP, "\n");
13711     }
13712
13713       while(*p==' ') p++;
13714     }
13715
13716     /* read e.p. field in games that know e.p. capture */
13717     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13718        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13719       if(*p=='-') {
13720         p++; FENepStatus = EP_NONE;
13721       } else {
13722          char c = *p++ - AAA;
13723
13724          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13725          if(*p >= '0' && *p <='9') *p++;
13726          FENepStatus = c;
13727       }
13728     }
13729
13730
13731     if(sscanf(p, "%d", &i) == 1) {
13732         FENrulePlies = i; /* 50-move ply counter */
13733         /* (The move number is still ignored)    */
13734     }
13735
13736     return TRUE;
13737 }
13738       
13739 void
13740 EditPositionPasteFEN(char *fen)
13741 {
13742   if (fen != NULL) {
13743     Board initial_position;
13744
13745     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13746       DisplayError(_("Bad FEN position in clipboard"), 0);
13747       return ;
13748     } else {
13749       int savedBlackPlaysFirst = blackPlaysFirst;
13750       EditPositionEvent();
13751       blackPlaysFirst = savedBlackPlaysFirst;
13752       CopyBoard(boards[0], initial_position);
13753           /* [HGM] copy FEN attributes as well */
13754           {   int i;
13755               initialRulePlies = FENrulePlies;
13756               epStatus[0] = FENepStatus;
13757               for( i=0; i<nrCastlingRights; i++ )
13758                   castlingRights[0][i] = FENcastlingRights[i];
13759           }
13760       EditPositionDone();
13761       DisplayBothClocks();
13762       DrawPosition(FALSE, boards[currentMove]);
13763     }
13764   }
13765 }