fixed some implicit declarations reported by Stanislav Brabec
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 #else /* not STDC_HEADERS */
81 # if HAVE_STRING_H
82 #  include <string.h>
83 # else /* not HAVE_STRING_H */
84 #  include <strings.h>
85 # endif /* not HAVE_STRING_H */
86 #endif /* not STDC_HEADERS */
87
88 #if HAVE_SYS_FCNTL_H
89 # include <sys/fcntl.h>
90 #else /* not HAVE_SYS_FCNTL_H */
91 # if HAVE_FCNTL_H
92 #  include <fcntl.h>
93 # endif /* HAVE_FCNTL_H */
94 #endif /* not HAVE_SYS_FCNTL_H */
95
96 #if TIME_WITH_SYS_TIME
97 # include <sys/time.h>
98 # include <time.h>
99 #else
100 # if HAVE_SYS_TIME_H
101 #  include <sys/time.h>
102 # else
103 #  include <time.h>
104 # endif
105 #endif
106
107 #if defined(_amigados) && !defined(__GNUC__)
108 struct timezone {
109     int tz_minuteswest;
110     int tz_dsttime;
111 };
112 extern int gettimeofday(struct timeval *, struct timezone *);
113 #endif
114
115 #if HAVE_UNISTD_H
116 # include <unistd.h>
117 #endif
118
119 #include "common.h"
120 #include "frontend.h"
121 #include "backend.h"
122 #include "parser.h"
123 #include "moves.h"
124 #if ZIPPY
125 # include "zippy.h"
126 #endif
127 #include "backendz.h"
128 #include "gettext.h" 
129  
130 #ifdef ENABLE_NLS 
131 # define _(s) gettext (s) 
132 # define N_(s) gettext_noop (s) 
133 #else 
134 # define _(s) (s) 
135 # define N_(s) s 
136 #endif 
137
138
139 /* A point in time */
140 typedef struct {
141     long sec;  /* Assuming this is >= 32 bits */
142     int ms;    /* Assuming this is >= 16 bits */
143 } TimeMark;
144
145 int establish P((void));
146 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
147                          char *buf, int count, int error));
148 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
149                       char *buf, int count, int error));
150 void SendToICS P((char *s));
151 void SendToICSDelayed P((char *s, long msdelay));
152 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
153                       int toX, int toY));
154 void HandleMachineMove P((char *message, ChessProgramState *cps));
155 int AutoPlayOneMove P((void));
156 int LoadGameOneMove P((ChessMove readAhead));
157 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
158 int LoadPositionFromFile P((char *filename, int n, char *title));
159 int SavePositionToFile P((char *filename));
160 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
161                   Board board, char *castle, char *ep));
162 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
163 void ShowMove P((int fromX, int fromY, int toX, int toY));
164 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
165                    /*char*/int promoChar));
166 void BackwardInner P((int target));
167 void ForwardInner P((int target));
168 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
169 void EditPositionDone P((void));
170 void PrintOpponents P((FILE *fp));
171 void PrintPosition P((FILE *fp, int move));
172 void StartChessProgram P((ChessProgramState *cps));
173 void SendToProgram P((char *message, ChessProgramState *cps));
174 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
175 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
176                            char *buf, int count, int error));
177 void SendTimeControl P((ChessProgramState *cps,
178                         int mps, long tc, int inc, int sd, int st));
179 char *TimeControlTagValue P((void));
180 void Attention P((ChessProgramState *cps));
181 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
182 void ResurrectChessProgram P((void));
183 void DisplayComment P((int moveNumber, char *text));
184 void DisplayMove P((int moveNumber));
185 void DisplayAnalysis P((void));
186
187 void ParseGameHistory P((char *game));
188 void ParseBoard12 P((char *string));
189 void StartClocks P((void));
190 void SwitchClocks P((void));
191 void StopClocks P((void));
192 void ResetClocks P((void));
193 char *PGNDate P((void));
194 void SetGameInfo P((void));
195 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
196 int RegisterMove P((void));
197 void MakeRegisteredMove P((void));
198 void TruncateGame P((void));
199 int looking_at P((char *, int *, char *));
200 void CopyPlayerNameIntoFileName P((char **, char *));
201 char *SavePart P((char *));
202 int SaveGameOldStyle P((FILE *));
203 int SaveGamePGN P((FILE *));
204 void GetTimeMark P((TimeMark *));
205 long SubtractTimeMarks P((TimeMark *, TimeMark *));
206 int CheckFlags P((void));
207 long NextTickLength P((long));
208 void CheckTimeControl P((void));
209 void show_bytes P((FILE *, char *, int));
210 int string_to_rating P((char *str));
211 void ParseFeatures P((char* args, ChessProgramState *cps));
212 void InitBackEnd3 P((void));
213 void FeatureDone P((ChessProgramState* cps, int val));
214 void InitChessProgram P((ChessProgramState *cps, int setup));
215 void OutputKibitz(int window, char *text);
216 int PerpetualChase(int first, int last);
217 int EngineOutputIsUp();
218 void InitDrawingSizes(int x, int y);
219
220 #ifdef WIN32
221        extern void ConsoleCreate();
222 #endif
223
224 ChessProgramState *WhitePlayer();
225 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
226 int VerifyDisplayMode P(());
227
228 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
229 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
230 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
231 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
232 extern char installDir[MSG_SIZ];
233
234 extern int tinyLayout, smallLayout;
235 ChessProgramStats programStats;
236 static int exiting = 0; /* [HGM] moved to top */
237 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
238 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
239 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
240 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
241 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
242 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
243 int opponentKibitzes;
244 int lastSavedGame; /* [HGM] save: ID of game */
245
246 /* States for ics_getting_history */
247 #define H_FALSE 0
248 #define H_REQUESTED 1
249 #define H_GOT_REQ_HEADER 2
250 #define H_GOT_UNREQ_HEADER 3
251 #define H_GETTING_MOVES 4
252 #define H_GOT_UNWANTED_HEADER 5
253
254 /* whosays values for GameEnds */
255 #define GE_ICS 0
256 #define GE_ENGINE 1
257 #define GE_PLAYER 2
258 #define GE_FILE 3
259 #define GE_XBOARD 4
260 #define GE_ENGINE1 5
261 #define GE_ENGINE2 6
262
263 /* Maximum number of games in a cmail message */
264 #define CMAIL_MAX_GAMES 20
265
266 /* Different types of move when calling RegisterMove */
267 #define CMAIL_MOVE   0
268 #define CMAIL_RESIGN 1
269 #define CMAIL_DRAW   2
270 #define CMAIL_ACCEPT 3
271
272 /* Different types of result to remember for each game */
273 #define CMAIL_NOT_RESULT 0
274 #define CMAIL_OLD_RESULT 1
275 #define CMAIL_NEW_RESULT 2
276
277 /* Telnet protocol constants */
278 #define TN_WILL 0373
279 #define TN_WONT 0374
280 #define TN_DO   0375
281 #define TN_DONT 0376
282 #define TN_IAC  0377
283 #define TN_ECHO 0001
284 #define TN_SGA  0003
285 #define TN_PORT 23
286
287 /* [AS] */
288 static char * safeStrCpy( char * dst, const char * src, size_t count )
289 {
290     assert( dst != NULL );
291     assert( src != NULL );
292     assert( count > 0 );
293
294     strncpy( dst, src, count );
295     dst[ count-1 ] = '\0';
296     return dst;
297 }
298
299 #if 0
300 //[HGM] for future use? Conditioned out for now to suppress warning.
301 static char * safeStrCat( char * dst, const char * src, size_t count )
302 {
303     size_t  dst_len;
304
305     assert( dst != NULL );
306     assert( src != NULL );
307     assert( count > 0 );
308
309     dst_len = strlen(dst);
310
311     assert( count > dst_len ); /* Buffer size must be greater than current length */
312
313     safeStrCpy( dst + dst_len, src, count - dst_len );
314
315     return dst;
316 }
317 #endif
318
319 /* Some compiler can't cast u64 to double
320  * This function do the job for us:
321
322  * We use the highest bit for cast, this only
323  * works if the highest bit is not
324  * in use (This should not happen)
325  *
326  * We used this for all compiler
327  */
328 double
329 u64ToDouble(u64 value)
330 {
331   double r;
332   u64 tmp = value & u64Const(0x7fffffffffffffff);
333   r = (double)(s64)tmp;
334   if (value & u64Const(0x8000000000000000))
335        r +=  9.2233720368547758080e18; /* 2^63 */
336  return r;
337 }
338
339 /* Fake up flags for now, as we aren't keeping track of castling
340    availability yet. [HGM] Change of logic: the flag now only
341    indicates the type of castlings allowed by the rule of the game.
342    The actual rights themselves are maintained in the array
343    castlingRights, as part of the game history, and are not probed
344    by this function.
345  */
346 int
347 PosFlags(index)
348 {
349   int flags = F_ALL_CASTLE_OK;
350   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
351   switch (gameInfo.variant) {
352   case VariantSuicide:
353     flags &= ~F_ALL_CASTLE_OK;
354   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
355     flags |= F_IGNORE_CHECK;
356   case VariantLosers:
357     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
358     break;
359   case VariantAtomic:
360     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
361     break;
362   case VariantKriegspiel:
363     flags |= F_KRIEGSPIEL_CAPTURE;
364     break;
365   case VariantCapaRandom: 
366   case VariantFischeRandom:
367     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
368   case VariantNoCastle:
369   case VariantShatranj:
370   case VariantCourier:
371     flags &= ~F_ALL_CASTLE_OK;
372     break;
373   default:
374     break;
375   }
376   return flags;
377 }
378
379 FILE *gameFileFP, *debugFP;
380
381 /* 
382     [AS] Note: sometimes, the sscanf() function is used to parse the input
383     into a fixed-size buffer. Because of this, we must be prepared to
384     receive strings as long as the size of the input buffer, which is currently
385     set to 4K for Windows and 8K for the rest.
386     So, we must either allocate sufficiently large buffers here, or
387     reduce the size of the input buffer in the input reading part.
388 */
389
390 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
391 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
392 char thinkOutput1[MSG_SIZ*10];
393
394 ChessProgramState first, second;
395
396 /* premove variables */
397 int premoveToX = 0;
398 int premoveToY = 0;
399 int premoveFromX = 0;
400 int premoveFromY = 0;
401 int premovePromoChar = 0;
402 int gotPremove = 0;
403 Boolean alarmSounded;
404 /* end premove variables */
405
406 char *ics_prefix = "$";
407 int ics_type = ICS_GENERIC;
408
409 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
410 int pauseExamForwardMostMove = 0;
411 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
412 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
413 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
414 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
415 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
416 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
417 int whiteFlag = FALSE, blackFlag = FALSE;
418 int userOfferedDraw = FALSE;
419 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
420 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
421 int cmailMoveType[CMAIL_MAX_GAMES];
422 long ics_clock_paused = 0;
423 ProcRef icsPR = NoProc, cmailPR = NoProc;
424 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
425 GameMode gameMode = BeginningOfGame;
426 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
427 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
428 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
429 int hiddenThinkOutputState = 0; /* [AS] */
430 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
431 int adjudicateLossPlies = 6;
432 char white_holding[64], black_holding[64];
433 TimeMark lastNodeCountTime;
434 long lastNodeCount=0;
435 int have_sent_ICS_logon = 0;
436 int movesPerSession;
437 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
438 long timeControl_2; /* [AS] Allow separate time controls */
439 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
440 long timeRemaining[2][MAX_MOVES];
441 int matchGame = 0;
442 TimeMark programStartTime;
443 char ics_handle[MSG_SIZ];
444 int have_set_title = 0;
445
446 /* animateTraining preserves the state of appData.animate
447  * when Training mode is activated. This allows the
448  * response to be animated when appData.animate == TRUE and
449  * appData.animateDragging == TRUE.
450  */
451 Boolean animateTraining;
452
453 GameInfo gameInfo;
454
455 AppData appData;
456
457 Board boards[MAX_MOVES];
458 /* [HGM] Following 7 needed for accurate legality tests: */
459 char  epStatus[MAX_MOVES];
460 char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
461 char  castlingRank[BOARD_SIZE]; // and corresponding ranks
462 char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
463 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
464 int   initialRulePlies, FENrulePlies;
465 char  FENepStatus;
466 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
467 int loadFlag = 0; 
468 int shuffleOpenings;
469
470 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
471     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
472         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
473     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
474         BlackKing, BlackBishop, BlackKnight, BlackRook }
475 };
476
477 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
478     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
479         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
480     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
481         BlackKing, BlackKing, BlackKnight, BlackRook }
482 };
483
484 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
485     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
486         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
487     { BlackRook, BlackMan, BlackBishop, BlackQueen,
488         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
489 };
490
491 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
492     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
493         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
494     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
495         BlackKing, BlackBishop, BlackKnight, BlackRook }
496 };
497
498 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
499     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
500         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
501     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
502         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
503 };
504
505
506 #if (BOARD_SIZE>=10)
507 ChessSquare ShogiArray[2][BOARD_SIZE] = {
508     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
509         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
510     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
511         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
512 };
513
514 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
515     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
516         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
517     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
518         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
519 };
520
521 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
522     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
523         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
524     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
525         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
526 };
527
528 ChessSquare GreatArray[2][BOARD_SIZE] = {
529     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
530         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
531     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
532         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
533 };
534
535 ChessSquare JanusArray[2][BOARD_SIZE] = {
536     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
537         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
538     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
539         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
540 };
541
542 #ifdef GOTHIC
543 ChessSquare GothicArray[2][BOARD_SIZE] = {
544     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
545         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
546     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
547         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
548 };
549 #else // !GOTHIC
550 #define GothicArray CapablancaArray
551 #endif // !GOTHIC
552
553 #ifdef FALCON
554 ChessSquare FalconArray[2][BOARD_SIZE] = {
555     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
556         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
557     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
558         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
559 };
560 #else // !FALCON
561 #define FalconArray CapablancaArray
562 #endif // !FALCON
563
564 #else // !(BOARD_SIZE>=10)
565 #define XiangqiPosition FIDEArray
566 #define CapablancaArray FIDEArray
567 #define GothicArray FIDEArray
568 #define GreatArray FIDEArray
569 #endif // !(BOARD_SIZE>=10)
570
571 #if (BOARD_SIZE>=12)
572 ChessSquare CourierArray[2][BOARD_SIZE] = {
573     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
574         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
575     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
576         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
577 };
578 #else // !(BOARD_SIZE>=12)
579 #define CourierArray CapablancaArray
580 #endif // !(BOARD_SIZE>=12)
581
582
583 Board initialPosition;
584
585
586 /* Convert str to a rating. Checks for special cases of "----",
587
588    "++++", etc. Also strips ()'s */
589 int
590 string_to_rating(str)
591   char *str;
592 {
593   while(*str && !isdigit(*str)) ++str;
594   if (!*str)
595     return 0;   /* One of the special "no rating" cases */
596   else
597     return atoi(str);
598 }
599
600 void
601 ClearProgramStats()
602 {
603     /* Init programStats */
604     programStats.movelist[0] = 0;
605     programStats.depth = 0;
606     programStats.nr_moves = 0;
607     programStats.moves_left = 0;
608     programStats.nodes = 0;
609     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
610     programStats.score = 0;
611     programStats.got_only_move = 0;
612     programStats.got_fail = 0;
613     programStats.line_is_book = 0;
614 }
615
616 void
617 InitBackEnd1()
618 {
619     int matched, min, sec;
620
621     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
622
623     GetTimeMark(&programStartTime);
624     srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
625
626     ClearProgramStats();
627     programStats.ok_to_send = 1;
628     programStats.seen_stat = 0;
629
630     /*
631      * Initialize game list
632      */
633     ListNew(&gameList);
634
635
636     /*
637      * Internet chess server status
638      */
639     if (appData.icsActive) {
640         appData.matchMode = FALSE;
641         appData.matchGames = 0;
642 #if ZIPPY       
643         appData.noChessProgram = !appData.zippyPlay;
644 #else
645         appData.zippyPlay = FALSE;
646         appData.zippyTalk = FALSE;
647         appData.noChessProgram = TRUE;
648 #endif
649         if (*appData.icsHelper != NULLCHAR) {
650             appData.useTelnet = TRUE;
651             appData.telnetProgram = appData.icsHelper;
652         }
653     } else {
654         appData.zippyTalk = appData.zippyPlay = FALSE;
655     }
656
657     /* [AS] Initialize pv info list [HGM] and game state */
658     {
659         int i, j;
660
661         for( i=0; i<MAX_MOVES; i++ ) {
662             pvInfoList[i].depth = -1;
663             epStatus[i]=EP_NONE;
664             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
665         }
666     }
667
668     /*
669      * Parse timeControl resource
670      */
671     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
672                           appData.movesPerSession)) {
673         char buf[MSG_SIZ];
674         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
675         DisplayFatalError(buf, 0, 2);
676     }
677
678     /*
679      * Parse searchTime resource
680      */
681     if (*appData.searchTime != NULLCHAR) {
682         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
683         if (matched == 1) {
684             searchTime = min * 60;
685         } else if (matched == 2) {
686             searchTime = min * 60 + sec;
687         } else {
688             char buf[MSG_SIZ];
689             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
690             DisplayFatalError(buf, 0, 2);
691         }
692     }
693
694     /* [AS] Adjudication threshold */
695     adjudicateLossThreshold = appData.adjudicateLossThreshold;
696     
697     first.which = "first";
698     second.which = "second";
699     first.maybeThinking = second.maybeThinking = FALSE;
700     first.pr = second.pr = NoProc;
701     first.isr = second.isr = NULL;
702     first.sendTime = second.sendTime = 2;
703     first.sendDrawOffers = 1;
704     if (appData.firstPlaysBlack) {
705         first.twoMachinesColor = "black\n";
706         second.twoMachinesColor = "white\n";
707     } else {
708         first.twoMachinesColor = "white\n";
709         second.twoMachinesColor = "black\n";
710     }
711     first.program = appData.firstChessProgram;
712     second.program = appData.secondChessProgram;
713     first.host = appData.firstHost;
714     second.host = appData.secondHost;
715     first.dir = appData.firstDirectory;
716     second.dir = appData.secondDirectory;
717     first.other = &second;
718     second.other = &first;
719     first.initString = appData.initString;
720     second.initString = appData.secondInitString;
721     first.computerString = appData.firstComputerString;
722     second.computerString = appData.secondComputerString;
723     first.useSigint = second.useSigint = TRUE;
724     first.useSigterm = second.useSigterm = TRUE;
725     first.reuse = appData.reuseFirst;
726     second.reuse = appData.reuseSecond;
727     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
728     second.nps = appData.secondNPS;
729     first.useSetboard = second.useSetboard = FALSE;
730     first.useSAN = second.useSAN = FALSE;
731     first.usePing = second.usePing = FALSE;
732     first.lastPing = second.lastPing = 0;
733     first.lastPong = second.lastPong = 0;
734     first.usePlayother = second.usePlayother = FALSE;
735     first.useColors = second.useColors = TRUE;
736     first.useUsermove = second.useUsermove = FALSE;
737     first.sendICS = second.sendICS = FALSE;
738     first.sendName = second.sendName = appData.icsActive;
739     first.sdKludge = second.sdKludge = FALSE;
740     first.stKludge = second.stKludge = FALSE;
741     TidyProgramName(first.program, first.host, first.tidy);
742     TidyProgramName(second.program, second.host, second.tidy);
743     first.matchWins = second.matchWins = 0;
744     strcpy(first.variants, appData.variant);
745     strcpy(second.variants, appData.variant);
746     first.analysisSupport = second.analysisSupport = 2; /* detect */
747     first.analyzing = second.analyzing = FALSE;
748     first.initDone = second.initDone = FALSE;
749
750     /* New features added by Tord: */
751     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
752     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
753     /* End of new features added by Tord. */
754     first.fenOverride  = appData.fenOverride1;
755     second.fenOverride = appData.fenOverride2;
756
757     /* [HGM] time odds: set factor for each machine */
758     first.timeOdds  = appData.firstTimeOdds;
759     second.timeOdds = appData.secondTimeOdds;
760     { int norm = 1;
761         if(appData.timeOddsMode) {
762             norm = first.timeOdds;
763             if(norm > second.timeOdds) norm = second.timeOdds;
764         }
765         first.timeOdds /= norm;
766         second.timeOdds /= norm;
767     }
768
769     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
770     first.accumulateTC = appData.firstAccumulateTC;
771     second.accumulateTC = appData.secondAccumulateTC;
772     first.maxNrOfSessions = second.maxNrOfSessions = 1;
773
774     /* [HGM] debug */
775     first.debug = second.debug = FALSE;
776     first.supportsNPS = second.supportsNPS = UNKNOWN;
777
778     /* [HGM] options */
779     first.optionSettings  = appData.firstOptions;
780     second.optionSettings = appData.secondOptions;
781
782     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
783     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
784     first.isUCI = appData.firstIsUCI; /* [AS] */
785     second.isUCI = appData.secondIsUCI; /* [AS] */
786     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
787     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
788
789     if (appData.firstProtocolVersion > PROTOVER ||
790         appData.firstProtocolVersion < 1) {
791       char buf[MSG_SIZ];
792       sprintf(buf, _("protocol version %d not supported"),
793               appData.firstProtocolVersion);
794       DisplayFatalError(buf, 0, 2);
795     } else {
796       first.protocolVersion = appData.firstProtocolVersion;
797     }
798
799     if (appData.secondProtocolVersion > PROTOVER ||
800         appData.secondProtocolVersion < 1) {
801       char buf[MSG_SIZ];
802       sprintf(buf, _("protocol version %d not supported"),
803               appData.secondProtocolVersion);
804       DisplayFatalError(buf, 0, 2);
805     } else {
806       second.protocolVersion = appData.secondProtocolVersion;
807     }
808
809     if (appData.icsActive) {
810         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
811     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
812         appData.clockMode = FALSE;
813         first.sendTime = second.sendTime = 0;
814     }
815     
816 #if ZIPPY
817     /* Override some settings from environment variables, for backward
818        compatibility.  Unfortunately it's not feasible to have the env
819        vars just set defaults, at least in xboard.  Ugh.
820     */
821     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
822       ZippyInit();
823     }
824 #endif
825     
826     if (appData.noChessProgram) {
827         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
828         sprintf(programVersion, "%s", PACKAGE_STRING);
829     } else {
830 #if 0
831         char *p, *q;
832         q = first.program;
833         while (*q != ' ' && *q != NULLCHAR) q++;
834         p = q;
835         while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] backslash added */
836         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING + (q - p));
837         sprintf(programVersion, "%s + ", PACKAGE_STRING);
838         strncat(programVersion, p, q - p);
839 #else
840         /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
841         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
842         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
843 #endif
844     }
845
846     if (!appData.icsActive) {
847       char buf[MSG_SIZ];
848       /* Check for variants that are supported only in ICS mode,
849          or not at all.  Some that are accepted here nevertheless
850          have bugs; see comments below.
851       */
852       VariantClass variant = StringToVariant(appData.variant);
853       switch (variant) {
854       case VariantBughouse:     /* need four players and two boards */
855       case VariantKriegspiel:   /* need to hide pieces and move details */
856       /* case VariantFischeRandom: (Fabien: moved below) */
857         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
858         DisplayFatalError(buf, 0, 2);
859         return;
860
861       case VariantUnknown:
862       case VariantLoadable:
863       case Variant29:
864       case Variant30:
865       case Variant31:
866       case Variant32:
867       case Variant33:
868       case Variant34:
869       case Variant35:
870       case Variant36:
871       default:
872         sprintf(buf, _("Unknown variant name %s"), appData.variant);
873         DisplayFatalError(buf, 0, 2);
874         return;
875
876       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
877       case VariantFairy:      /* [HGM] TestLegality definitely off! */
878       case VariantGothic:     /* [HGM] should work */
879       case VariantCapablanca: /* [HGM] should work */
880       case VariantCourier:    /* [HGM] initial forced moves not implemented */
881       case VariantShogi:      /* [HGM] drops not tested for legality */
882       case VariantKnightmate: /* [HGM] should work */
883       case VariantCylinder:   /* [HGM] untested */
884       case VariantFalcon:     /* [HGM] untested */
885       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
886                                  offboard interposition not understood */
887       case VariantNormal:     /* definitely works! */
888       case VariantWildCastle: /* pieces not automatically shuffled */
889       case VariantNoCastle:   /* pieces not automatically shuffled */
890       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
891       case VariantLosers:     /* should work except for win condition,
892                                  and doesn't know captures are mandatory */
893       case VariantSuicide:    /* should work except for win condition,
894                                  and doesn't know captures are mandatory */
895       case VariantGiveaway:   /* should work except for win condition,
896                                  and doesn't know captures are mandatory */
897       case VariantTwoKings:   /* should work */
898       case VariantAtomic:     /* should work except for win condition */
899       case Variant3Check:     /* should work except for win condition */
900       case VariantShatranj:   /* should work except for all win conditions */
901       case VariantBerolina:   /* might work if TestLegality is off */
902       case VariantCapaRandom: /* should work */
903       case VariantJanus:      /* should work */
904       case VariantSuper:      /* experimental */
905       case VariantGreat:      /* experimental, requires legality testing to be off */
906         break;
907       }
908     }
909
910     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
911     InitEngineUCI( installDir, &second );
912 }
913
914 int NextIntegerFromString( char ** str, long * value )
915 {
916     int result = -1;
917     char * s = *str;
918
919     while( *s == ' ' || *s == '\t' ) {
920         s++;
921     }
922
923     *value = 0;
924
925     if( *s >= '0' && *s <= '9' ) {
926         while( *s >= '0' && *s <= '9' ) {
927             *value = *value * 10 + (*s - '0');
928             s++;
929         }
930
931         result = 0;
932     }
933
934     *str = s;
935
936     return result;
937 }
938
939 int NextTimeControlFromString( char ** str, long * value )
940 {
941     long temp;
942     int result = NextIntegerFromString( str, &temp );
943
944     if( result == 0 ) {
945         *value = temp * 60; /* Minutes */
946         if( **str == ':' ) {
947             (*str)++;
948             result = NextIntegerFromString( str, &temp );
949             *value += temp; /* Seconds */
950         }
951     }
952
953     return result;
954 }
955
956 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
957 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
958     int result = -1; long temp, temp2;
959
960     if(**str != '+') return -1; // old params remain in force!
961     (*str)++;
962     if( NextTimeControlFromString( str, &temp ) ) return -1;
963
964     if(**str != '/') {
965         /* time only: incremental or sudden-death time control */
966         if(**str == '+') { /* increment follows; read it */
967             (*str)++;
968             if(result = NextIntegerFromString( str, &temp2)) return -1;
969             *inc = temp2 * 1000;
970         } else *inc = 0;
971         *moves = 0; *tc = temp * 1000; 
972         return 0;
973     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
974
975     (*str)++; /* classical time control */
976     result = NextTimeControlFromString( str, &temp2);
977     if(result == 0) {
978         *moves = temp/60;
979         *tc    = temp2 * 1000;
980         *inc   = 0;
981     }
982     return result;
983 }
984
985 int GetTimeQuota(int movenr)
986 {   /* [HGM] get time to add from the multi-session time-control string */
987     int moves=1; /* kludge to force reading of first session */
988     long time, increment;
989     char *s = fullTimeControlString;
990
991     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
992     do {
993         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
994         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
995         if(movenr == -1) return time;    /* last move before new session     */
996         if(!moves) return increment;     /* current session is incremental   */
997         if(movenr >= 0) movenr -= moves; /* we already finished this session */
998     } while(movenr >= -1);               /* try again for next session       */
999
1000     return 0; // no new time quota on this move
1001 }
1002
1003 int
1004 ParseTimeControl(tc, ti, mps)
1005      char *tc;
1006      int ti;
1007      int mps;
1008 {
1009 #if 0
1010     int matched, min, sec;
1011
1012     matched = sscanf(tc, "%d:%d", &min, &sec);
1013     if (matched == 1) {
1014         timeControl = min * 60 * 1000;
1015     } else if (matched == 2) {
1016         timeControl = (min * 60 + sec) * 1000;
1017     } else {
1018         return FALSE;
1019     }
1020 #else
1021     long tc1;
1022     long tc2;
1023     char buf[MSG_SIZ];
1024
1025     if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1026     if(ti > 0) {
1027         if(mps)
1028              sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1029         else sprintf(buf, "+%s+%d", tc, ti);
1030     } else {
1031         if(mps)
1032              sprintf(buf, "+%d/%s", mps, tc);
1033         else sprintf(buf, "+%s", tc);
1034     }
1035     fullTimeControlString = StrSave(buf);
1036
1037     if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1038         return FALSE;
1039     }
1040
1041     if( *tc == '/' ) {
1042         /* Parse second time control */
1043         tc++;
1044
1045         if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1046             return FALSE;
1047         }
1048
1049         if( tc2 == 0 ) {
1050             return FALSE;
1051         }
1052
1053         timeControl_2 = tc2 * 1000;
1054     }
1055     else {
1056         timeControl_2 = 0;
1057     }
1058
1059     if( tc1 == 0 ) {
1060         return FALSE;
1061     }
1062
1063     timeControl = tc1 * 1000;
1064 #endif
1065
1066     if (ti >= 0) {
1067         timeIncrement = ti * 1000;  /* convert to ms */
1068         movesPerSession = 0;
1069     } else {
1070         timeIncrement = 0;
1071         movesPerSession = mps;
1072     }
1073     return TRUE;
1074 }
1075
1076 void
1077 InitBackEnd2()
1078 {
1079     if (appData.debugMode) {
1080         fprintf(debugFP, "%s\n", programVersion);
1081     }
1082
1083     if (appData.matchGames > 0) {
1084         appData.matchMode = TRUE;
1085     } else if (appData.matchMode) {
1086         appData.matchGames = 1;
1087     }
1088     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1089         appData.matchGames = appData.sameColorGames;
1090     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1091         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1092         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1093     }
1094     Reset(TRUE, FALSE);
1095     if (appData.noChessProgram || first.protocolVersion == 1) {
1096       InitBackEnd3();
1097     } else {
1098       /* kludge: allow timeout for initial "feature" commands */
1099       FreezeUI();
1100       DisplayMessage("", _("Starting chess program"));
1101       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1102     }
1103 }
1104
1105 void
1106 InitBackEnd3 P((void))
1107 {
1108     GameMode initialMode;
1109     char buf[MSG_SIZ];
1110     int err;
1111
1112     InitChessProgram(&first, startedFromSetupPosition);
1113
1114
1115     if (appData.icsActive) {
1116 #ifdef WIN32
1117         /* [DM] Make a console window if needed [HGM] merged ifs */
1118         ConsoleCreate(); 
1119 #endif
1120         err = establish();
1121         if (err != 0) {
1122             if (*appData.icsCommPort != NULLCHAR) {
1123                 sprintf(buf, _("Could not open comm port %s"),  
1124                         appData.icsCommPort);
1125             } else {
1126                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1127                         appData.icsHost, appData.icsPort);
1128             }
1129             DisplayFatalError(buf, err, 1);
1130             return;
1131         }
1132         SetICSMode();
1133         telnetISR =
1134           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1135         fromUserISR =
1136           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1137     } else if (appData.noChessProgram) {
1138         SetNCPMode();
1139     } else {
1140         SetGNUMode();
1141     }
1142
1143     if (*appData.cmailGameName != NULLCHAR) {
1144         SetCmailMode();
1145         OpenLoopback(&cmailPR);
1146         cmailISR =
1147           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1148     }
1149     
1150     ThawUI();
1151     DisplayMessage("", "");
1152     if (StrCaseCmp(appData.initialMode, "") == 0) {
1153       initialMode = BeginningOfGame;
1154     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1155       initialMode = TwoMachinesPlay;
1156     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1157       initialMode = AnalyzeFile; 
1158     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1159       initialMode = AnalyzeMode;
1160     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1161       initialMode = MachinePlaysWhite;
1162     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1163       initialMode = MachinePlaysBlack;
1164     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1165       initialMode = EditGame;
1166     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1167       initialMode = EditPosition;
1168     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1169       initialMode = Training;
1170     } else {
1171       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1172       DisplayFatalError(buf, 0, 2);
1173       return;
1174     }
1175
1176     if (appData.matchMode) {
1177         /* Set up machine vs. machine match */
1178         if (appData.noChessProgram) {
1179             DisplayFatalError(_("Can't have a match with no chess programs"),
1180                               0, 2);
1181             return;
1182         }
1183         matchMode = TRUE;
1184         matchGame = 1;
1185         if (*appData.loadGameFile != NULLCHAR) {
1186             int index = appData.loadGameIndex; // [HGM] autoinc
1187             if(index<0) lastIndex = index = 1;
1188             if (!LoadGameFromFile(appData.loadGameFile,
1189                                   index,
1190                                   appData.loadGameFile, FALSE)) {
1191                 DisplayFatalError(_("Bad game file"), 0, 1);
1192                 return;
1193             }
1194         } else if (*appData.loadPositionFile != NULLCHAR) {
1195             int index = appData.loadPositionIndex; // [HGM] autoinc
1196             if(index<0) lastIndex = index = 1;
1197             if (!LoadPositionFromFile(appData.loadPositionFile,
1198                                       index,
1199                                       appData.loadPositionFile)) {
1200                 DisplayFatalError(_("Bad position file"), 0, 1);
1201                 return;
1202             }
1203         }
1204         TwoMachinesEvent();
1205     } else if (*appData.cmailGameName != NULLCHAR) {
1206         /* Set up cmail mode */
1207         ReloadCmailMsgEvent(TRUE);
1208     } else {
1209         /* Set up other modes */
1210         if (initialMode == AnalyzeFile) {
1211           if (*appData.loadGameFile == NULLCHAR) {
1212             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1213             return;
1214           }
1215         }
1216         if (*appData.loadGameFile != NULLCHAR) {
1217             (void) LoadGameFromFile(appData.loadGameFile,
1218                                     appData.loadGameIndex,
1219                                     appData.loadGameFile, TRUE);
1220         } else if (*appData.loadPositionFile != NULLCHAR) {
1221             (void) LoadPositionFromFile(appData.loadPositionFile,
1222                                         appData.loadPositionIndex,
1223                                         appData.loadPositionFile);
1224             /* [HGM] try to make self-starting even after FEN load */
1225             /* to allow automatic setup of fairy variants with wtm */
1226             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1227                 gameMode = BeginningOfGame;
1228                 setboardSpoiledMachineBlack = 1;
1229             }
1230             /* [HGM] loadPos: make that every new game uses the setup */
1231             /* from file as long as we do not switch variant          */
1232             if(!blackPlaysFirst) { int i;
1233                 startedFromPositionFile = TRUE;
1234                 CopyBoard(filePosition, boards[0]);
1235                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1236             }
1237         }
1238         if (initialMode == AnalyzeMode) {
1239           if (appData.noChessProgram) {
1240             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1241             return;
1242           }
1243           if (appData.icsActive) {
1244             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1245             return;
1246           }
1247           AnalyzeModeEvent();
1248         } else if (initialMode == AnalyzeFile) {
1249           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1250           ShowThinkingEvent();
1251           AnalyzeFileEvent();
1252           AnalysisPeriodicEvent(1);
1253         } else if (initialMode == MachinePlaysWhite) {
1254           if (appData.noChessProgram) {
1255             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1256                               0, 2);
1257             return;
1258           }
1259           if (appData.icsActive) {
1260             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1261                               0, 2);
1262             return;
1263           }
1264           MachineWhiteEvent();
1265         } else if (initialMode == MachinePlaysBlack) {
1266           if (appData.noChessProgram) {
1267             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1268                               0, 2);
1269             return;
1270           }
1271           if (appData.icsActive) {
1272             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1273                               0, 2);
1274             return;
1275           }
1276           MachineBlackEvent();
1277         } else if (initialMode == TwoMachinesPlay) {
1278           if (appData.noChessProgram) {
1279             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1280                               0, 2);
1281             return;
1282           }
1283           if (appData.icsActive) {
1284             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1285                               0, 2);
1286             return;
1287           }
1288           TwoMachinesEvent();
1289         } else if (initialMode == EditGame) {
1290           EditGameEvent();
1291         } else if (initialMode == EditPosition) {
1292           EditPositionEvent();
1293         } else if (initialMode == Training) {
1294           if (*appData.loadGameFile == NULLCHAR) {
1295             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1296             return;
1297           }
1298           TrainingEvent();
1299         }
1300     }
1301 }
1302
1303 /*
1304  * Establish will establish a contact to a remote host.port.
1305  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1306  *  used to talk to the host.
1307  * Returns 0 if okay, error code if not.
1308  */
1309 int
1310 establish()
1311 {
1312     char buf[MSG_SIZ];
1313
1314     if (*appData.icsCommPort != NULLCHAR) {
1315         /* Talk to the host through a serial comm port */
1316         return OpenCommPort(appData.icsCommPort, &icsPR);
1317
1318     } else if (*appData.gateway != NULLCHAR) {
1319         if (*appData.remoteShell == NULLCHAR) {
1320             /* Use the rcmd protocol to run telnet program on a gateway host */
1321             snprintf(buf, sizeof(buf), "%s %s %s",
1322                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1323             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1324
1325         } else {
1326             /* Use the rsh program to run telnet program on a gateway host */
1327             if (*appData.remoteUser == NULLCHAR) {
1328                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1329                         appData.gateway, appData.telnetProgram,
1330                         appData.icsHost, appData.icsPort);
1331             } else {
1332                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1333                         appData.remoteShell, appData.gateway, 
1334                         appData.remoteUser, appData.telnetProgram,
1335                         appData.icsHost, appData.icsPort);
1336             }
1337             return StartChildProcess(buf, "", &icsPR);
1338
1339         }
1340     } else if (appData.useTelnet) {
1341         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1342
1343     } else {
1344         /* TCP socket interface differs somewhat between
1345            Unix and NT; handle details in the front end.
1346            */
1347         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1348     }
1349 }
1350
1351 void
1352 show_bytes(fp, buf, count)
1353      FILE *fp;
1354      char *buf;
1355      int count;
1356 {
1357     while (count--) {
1358         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1359             fprintf(fp, "\\%03o", *buf & 0xff);
1360         } else {
1361             putc(*buf, fp);
1362         }
1363         buf++;
1364     }
1365     fflush(fp);
1366 }
1367
1368 /* Returns an errno value */
1369 int
1370 OutputMaybeTelnet(pr, message, count, outError)
1371      ProcRef pr;
1372      char *message;
1373      int count;
1374      int *outError;
1375 {
1376     char buf[8192], *p, *q, *buflim;
1377     int left, newcount, outcount;
1378
1379     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1380         *appData.gateway != NULLCHAR) {
1381         if (appData.debugMode) {
1382             fprintf(debugFP, ">ICS: ");
1383             show_bytes(debugFP, message, count);
1384             fprintf(debugFP, "\n");
1385         }
1386         return OutputToProcess(pr, message, count, outError);
1387     }
1388
1389     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1390     p = message;
1391     q = buf;
1392     left = count;
1393     newcount = 0;
1394     while (left) {
1395         if (q >= buflim) {
1396             if (appData.debugMode) {
1397                 fprintf(debugFP, ">ICS: ");
1398                 show_bytes(debugFP, buf, newcount);
1399                 fprintf(debugFP, "\n");
1400             }
1401             outcount = OutputToProcess(pr, buf, newcount, outError);
1402             if (outcount < newcount) return -1; /* to be sure */
1403             q = buf;
1404             newcount = 0;
1405         }
1406         if (*p == '\n') {
1407             *q++ = '\r';
1408             newcount++;
1409         } else if (((unsigned char) *p) == TN_IAC) {
1410             *q++ = (char) TN_IAC;
1411             newcount ++;
1412         }
1413         *q++ = *p++;
1414         newcount++;
1415         left--;
1416     }
1417     if (appData.debugMode) {
1418         fprintf(debugFP, ">ICS: ");
1419         show_bytes(debugFP, buf, newcount);
1420         fprintf(debugFP, "\n");
1421     }
1422     outcount = OutputToProcess(pr, buf, newcount, outError);
1423     if (outcount < newcount) return -1; /* to be sure */
1424     return count;
1425 }
1426
1427 void
1428 read_from_player(isr, closure, message, count, error)
1429      InputSourceRef isr;
1430      VOIDSTAR closure;
1431      char *message;
1432      int count;
1433      int error;
1434 {
1435     int outError, outCount;
1436     static int gotEof = 0;
1437
1438     /* Pass data read from player on to ICS */
1439     if (count > 0) {
1440         gotEof = 0;
1441         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1442         if (outCount < count) {
1443             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1444         }
1445     } else if (count < 0) {
1446         RemoveInputSource(isr);
1447         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1448     } else if (gotEof++ > 0) {
1449         RemoveInputSource(isr);
1450         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1451     }
1452 }
1453
1454 void
1455 SendToICS(s)
1456      char *s;
1457 {
1458     int count, outCount, outError;
1459
1460     if (icsPR == NULL) return;
1461
1462     count = strlen(s);
1463     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1464     if (outCount < count) {
1465         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1466     }
1467 }
1468
1469 /* This is used for sending logon scripts to the ICS. Sending
1470    without a delay causes problems when using timestamp on ICC
1471    (at least on my machine). */
1472 void
1473 SendToICSDelayed(s,msdelay)
1474      char *s;
1475      long msdelay;
1476 {
1477     int count, outCount, outError;
1478
1479     if (icsPR == NULL) return;
1480
1481     count = strlen(s);
1482     if (appData.debugMode) {
1483         fprintf(debugFP, ">ICS: ");
1484         show_bytes(debugFP, s, count);
1485         fprintf(debugFP, "\n");
1486     }
1487     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1488                                       msdelay);
1489     if (outCount < count) {
1490         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1491     }
1492 }
1493
1494
1495 /* Remove all highlighting escape sequences in s
1496    Also deletes any suffix starting with '(' 
1497    */
1498 char *
1499 StripHighlightAndTitle(s)
1500      char *s;
1501 {
1502     static char retbuf[MSG_SIZ];
1503     char *p = retbuf;
1504
1505     while (*s != NULLCHAR) {
1506         while (*s == '\033') {
1507             while (*s != NULLCHAR && !isalpha(*s)) s++;
1508             if (*s != NULLCHAR) s++;
1509         }
1510         while (*s != NULLCHAR && *s != '\033') {
1511             if (*s == '(' || *s == '[') {
1512                 *p = NULLCHAR;
1513                 return retbuf;
1514             }
1515             *p++ = *s++;
1516         }
1517     }
1518     *p = NULLCHAR;
1519     return retbuf;
1520 }
1521
1522 /* Remove all highlighting escape sequences in s */
1523 char *
1524 StripHighlight(s)
1525      char *s;
1526 {
1527     static char retbuf[MSG_SIZ];
1528     char *p = retbuf;
1529
1530     while (*s != NULLCHAR) {
1531         while (*s == '\033') {
1532             while (*s != NULLCHAR && !isalpha(*s)) s++;
1533             if (*s != NULLCHAR) s++;
1534         }
1535         while (*s != NULLCHAR && *s != '\033') {
1536             *p++ = *s++;
1537         }
1538     }
1539     *p = NULLCHAR;
1540     return retbuf;
1541 }
1542
1543 char *variantNames[] = VARIANT_NAMES;
1544 char *
1545 VariantName(v)
1546      VariantClass v;
1547 {
1548     return variantNames[v];
1549 }
1550
1551
1552 /* Identify a variant from the strings the chess servers use or the
1553    PGN Variant tag names we use. */
1554 VariantClass
1555 StringToVariant(e)
1556      char *e;
1557 {
1558     char *p;
1559     int wnum = -1;
1560     VariantClass v = VariantNormal;
1561     int i, found = FALSE;
1562     char buf[MSG_SIZ];
1563
1564     if (!e) return v;
1565
1566     /* [HGM] skip over optional board-size prefixes */
1567     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1568         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1569         while( *e++ != '_');
1570     }
1571
1572     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1573       if (StrCaseStr(e, variantNames[i])) {
1574         v = (VariantClass) i;
1575         found = TRUE;
1576         break;
1577       }
1578     }
1579
1580     if (!found) {
1581       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1582           || StrCaseStr(e, "wild/fr") 
1583           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1584         v = VariantFischeRandom;
1585       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1586                  (i = 1, p = StrCaseStr(e, "w"))) {
1587         p += i;
1588         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1589         if (isdigit(*p)) {
1590           wnum = atoi(p);
1591         } else {
1592           wnum = -1;
1593         }
1594         switch (wnum) {
1595         case 0: /* FICS only, actually */
1596         case 1:
1597           /* Castling legal even if K starts on d-file */
1598           v = VariantWildCastle;
1599           break;
1600         case 2:
1601         case 3:
1602         case 4:
1603           /* Castling illegal even if K & R happen to start in
1604              normal positions. */
1605           v = VariantNoCastle;
1606           break;
1607         case 5:
1608         case 7:
1609         case 8:
1610         case 10:
1611         case 11:
1612         case 12:
1613         case 13:
1614         case 14:
1615         case 15:
1616         case 18:
1617         case 19:
1618           /* Castling legal iff K & R start in normal positions */
1619           v = VariantNormal;
1620           break;
1621         case 6:
1622         case 20:
1623         case 21:
1624           /* Special wilds for position setup; unclear what to do here */
1625           v = VariantLoadable;
1626           break;
1627         case 9:
1628           /* Bizarre ICC game */
1629           v = VariantTwoKings;
1630           break;
1631         case 16:
1632           v = VariantKriegspiel;
1633           break;
1634         case 17:
1635           v = VariantLosers;
1636           break;
1637         case 22:
1638           v = VariantFischeRandom;
1639           break;
1640         case 23:
1641           v = VariantCrazyhouse;
1642           break;
1643         case 24:
1644           v = VariantBughouse;
1645           break;
1646         case 25:
1647           v = Variant3Check;
1648           break;
1649         case 26:
1650           /* Not quite the same as FICS suicide! */
1651           v = VariantGiveaway;
1652           break;
1653         case 27:
1654           v = VariantAtomic;
1655           break;
1656         case 28:
1657           v = VariantShatranj;
1658           break;
1659
1660         /* Temporary names for future ICC types.  The name *will* change in 
1661            the next xboard/WinBoard release after ICC defines it. */
1662         case 29:
1663           v = Variant29;
1664           break;
1665         case 30:
1666           v = Variant30;
1667           break;
1668         case 31:
1669           v = Variant31;
1670           break;
1671         case 32:
1672           v = Variant32;
1673           break;
1674         case 33:
1675           v = Variant33;
1676           break;
1677         case 34:
1678           v = Variant34;
1679           break;
1680         case 35:
1681           v = Variant35;
1682           break;
1683         case 36:
1684           v = Variant36;
1685           break;
1686         case 37:
1687           v = VariantShogi;
1688           break;
1689         case 38:
1690           v = VariantXiangqi;
1691           break;
1692         case 39:
1693           v = VariantCourier;
1694           break;
1695         case 40:
1696           v = VariantGothic;
1697           break;
1698         case 41:
1699           v = VariantCapablanca;
1700           break;
1701         case 42:
1702           v = VariantKnightmate;
1703           break;
1704         case 43:
1705           v = VariantFairy;
1706           break;
1707         case 44:
1708           v = VariantCylinder;
1709           break;
1710         case 45:
1711           v = VariantFalcon;
1712           break;
1713         case 46:
1714           v = VariantCapaRandom;
1715           break;
1716         case 47:
1717           v = VariantBerolina;
1718           break;
1719         case 48:
1720           v = VariantJanus;
1721           break;
1722         case 49:
1723           v = VariantSuper;
1724           break;
1725         case 50:
1726           v = VariantGreat;
1727           break;
1728         case -1:
1729           /* Found "wild" or "w" in the string but no number;
1730              must assume it's normal chess. */
1731           v = VariantNormal;
1732           break;
1733         default:
1734           sprintf(buf, _("Unknown wild type %d"), wnum);
1735           DisplayError(buf, 0);
1736           v = VariantUnknown;
1737           break;
1738         }
1739       }
1740     }
1741     if (appData.debugMode) {
1742       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1743               e, wnum, VariantName(v));
1744     }
1745     return v;
1746 }
1747
1748 static int leftover_start = 0, leftover_len = 0;
1749 char star_match[STAR_MATCH_N][MSG_SIZ];
1750
1751 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1752    advance *index beyond it, and set leftover_start to the new value of
1753    *index; else return FALSE.  If pattern contains the character '*', it
1754    matches any sequence of characters not containing '\r', '\n', or the
1755    character following the '*' (if any), and the matched sequence(s) are
1756    copied into star_match.
1757    */
1758 int
1759 looking_at(buf, index, pattern)
1760      char *buf;
1761      int *index;
1762      char *pattern;
1763 {
1764     char *bufp = &buf[*index], *patternp = pattern;
1765     int star_count = 0;
1766     char *matchp = star_match[0];
1767     
1768     for (;;) {
1769         if (*patternp == NULLCHAR) {
1770             *index = leftover_start = bufp - buf;
1771             *matchp = NULLCHAR;
1772             return TRUE;
1773         }
1774         if (*bufp == NULLCHAR) return FALSE;
1775         if (*patternp == '*') {
1776             if (*bufp == *(patternp + 1)) {
1777                 *matchp = NULLCHAR;
1778                 matchp = star_match[++star_count];
1779                 patternp += 2;
1780                 bufp++;
1781                 continue;
1782             } else if (*bufp == '\n' || *bufp == '\r') {
1783                 patternp++;
1784                 if (*patternp == NULLCHAR)
1785                   continue;
1786                 else
1787                   return FALSE;
1788             } else {
1789                 *matchp++ = *bufp++;
1790                 continue;
1791             }
1792         }
1793         if (*patternp != *bufp) return FALSE;
1794         patternp++;
1795         bufp++;
1796     }
1797 }
1798
1799 void
1800 SendToPlayer(data, length)
1801      char *data;
1802      int length;
1803 {
1804     int error, outCount;
1805     outCount = OutputToProcess(NoProc, data, length, &error);
1806     if (outCount < length) {
1807         DisplayFatalError(_("Error writing to display"), error, 1);
1808     }
1809 }
1810
1811 void
1812 PackHolding(packed, holding)
1813      char packed[];
1814      char *holding;
1815 {
1816     char *p = holding;
1817     char *q = packed;
1818     int runlength = 0;
1819     int curr = 9999;
1820     do {
1821         if (*p == curr) {
1822             runlength++;
1823         } else {
1824             switch (runlength) {
1825               case 0:
1826                 break;
1827               case 1:
1828                 *q++ = curr;
1829                 break;
1830               case 2:
1831                 *q++ = curr;
1832                 *q++ = curr;
1833                 break;
1834               default:
1835                 sprintf(q, "%d", runlength);
1836                 while (*q) q++;
1837                 *q++ = curr;
1838                 break;
1839             }
1840             runlength = 1;
1841             curr = *p;
1842         }
1843     } while (*p++);
1844     *q = NULLCHAR;
1845 }
1846
1847 /* Telnet protocol requests from the front end */
1848 void
1849 TelnetRequest(ddww, option)
1850      unsigned char ddww, option;
1851 {
1852     unsigned char msg[3];
1853     int outCount, outError;
1854
1855     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1856
1857     if (appData.debugMode) {
1858         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1859         switch (ddww) {
1860           case TN_DO:
1861             ddwwStr = "DO";
1862             break;
1863           case TN_DONT:
1864             ddwwStr = "DONT";
1865             break;
1866           case TN_WILL:
1867             ddwwStr = "WILL";
1868             break;
1869           case TN_WONT:
1870             ddwwStr = "WONT";
1871             break;
1872           default:
1873             ddwwStr = buf1;
1874             sprintf(buf1, "%d", ddww);
1875             break;
1876         }
1877         switch (option) {
1878           case TN_ECHO:
1879             optionStr = "ECHO";
1880             break;
1881           default:
1882             optionStr = buf2;
1883             sprintf(buf2, "%d", option);
1884             break;
1885         }
1886         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1887     }
1888     msg[0] = TN_IAC;
1889     msg[1] = ddww;
1890     msg[2] = option;
1891     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1892     if (outCount < 3) {
1893         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1894     }
1895 }
1896
1897 void
1898 DoEcho()
1899 {
1900     if (!appData.icsActive) return;
1901     TelnetRequest(TN_DO, TN_ECHO);
1902 }
1903
1904 void
1905 DontEcho()
1906 {
1907     if (!appData.icsActive) return;
1908     TelnetRequest(TN_DONT, TN_ECHO);
1909 }
1910
1911 void
1912 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1913 {
1914     /* put the holdings sent to us by the server on the board holdings area */
1915     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1916     char p;
1917     ChessSquare piece;
1918
1919     if(gameInfo.holdingsWidth < 2)  return;
1920
1921     if( (int)lowestPiece >= BlackPawn ) {
1922         holdingsColumn = 0;
1923         countsColumn = 1;
1924         holdingsStartRow = BOARD_HEIGHT-1;
1925         direction = -1;
1926     } else {
1927         holdingsColumn = BOARD_WIDTH-1;
1928         countsColumn = BOARD_WIDTH-2;
1929         holdingsStartRow = 0;
1930         direction = 1;
1931     }
1932
1933     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1934         board[i][holdingsColumn] = EmptySquare;
1935         board[i][countsColumn]   = (ChessSquare) 0;
1936     }
1937     while( (p=*holdings++) != NULLCHAR ) {
1938         piece = CharToPiece( ToUpper(p) );
1939         if(piece == EmptySquare) continue;
1940         /*j = (int) piece - (int) WhitePawn;*/
1941         j = PieceToNumber(piece);
1942         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1943         if(j < 0) continue;               /* should not happen */
1944         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1945         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1946         board[holdingsStartRow+j*direction][countsColumn]++;
1947     }
1948
1949 }
1950
1951
1952 void
1953 VariantSwitch(Board board, VariantClass newVariant)
1954 {
1955    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1956    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1957 //   Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1958
1959    startedFromPositionFile = FALSE;
1960    if(gameInfo.variant == newVariant) return;
1961
1962    /* [HGM] This routine is called each time an assignment is made to
1963     * gameInfo.variant during a game, to make sure the board sizes
1964     * are set to match the new variant. If that means adding or deleting
1965     * holdings, we shift the playing board accordingly
1966     * This kludge is needed because in ICS observe mode, we get boards
1967     * of an ongoing game without knowing the variant, and learn about the
1968     * latter only later. This can be because of the move list we requested,
1969     * in which case the game history is refilled from the beginning anyway,
1970     * but also when receiving holdings of a crazyhouse game. In the latter
1971     * case we want to add those holdings to the already received position.
1972     */
1973
1974
1975   if (appData.debugMode) {
1976     fprintf(debugFP, "Switch board from %s to %s\n",
1977                VariantName(gameInfo.variant), VariantName(newVariant));
1978     setbuf(debugFP, NULL);
1979   }
1980     shuffleOpenings = 0;       /* [HGM] shuffle */
1981     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1982     switch(newVariant) {
1983             case VariantShogi:
1984               newWidth = 9;  newHeight = 9;
1985               gameInfo.holdingsSize = 7;
1986             case VariantBughouse:
1987             case VariantCrazyhouse:
1988               newHoldingsWidth = 2; break;
1989             default:
1990               newHoldingsWidth = gameInfo.holdingsSize = 0;
1991     }
1992
1993     if(newWidth  != gameInfo.boardWidth  ||
1994        newHeight != gameInfo.boardHeight ||
1995        newHoldingsWidth != gameInfo.holdingsWidth ) {
1996
1997         /* shift position to new playing area, if needed */
1998         if(newHoldingsWidth > gameInfo.holdingsWidth) {
1999            for(i=0; i<BOARD_HEIGHT; i++) 
2000                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2001                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2002                                                      board[i][j];
2003            for(i=0; i<newHeight; i++) {
2004                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2005                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2006            }
2007         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2008            for(i=0; i<BOARD_HEIGHT; i++)
2009                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2010                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2011                                                  board[i][j];
2012         }
2013
2014         gameInfo.boardWidth  = newWidth;
2015         gameInfo.boardHeight = newHeight;
2016         gameInfo.holdingsWidth = newHoldingsWidth;
2017         gameInfo.variant = newVariant;
2018         InitDrawingSizes(-2, 0);
2019
2020         /* [HGM] The following should definitely be solved in a better way */
2021 #if 0
2022         CopyBoard(board, tempBoard); /* save position in case it is board[0] */
2023         for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
2024         saveEP = epStatus[0];
2025 #endif
2026         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2027 #if 0
2028         epStatus[0] = saveEP;
2029         for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
2030         CopyBoard(tempBoard, board); /* restore position received from ICS   */
2031 #endif
2032     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2033
2034     forwardMostMove = oldForwardMostMove;
2035     backwardMostMove = oldBackwardMostMove;
2036     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2037 }
2038
2039 static int loggedOn = FALSE;
2040
2041 /*-- Game start info cache: --*/
2042 int gs_gamenum;
2043 char gs_kind[MSG_SIZ];
2044 static char player1Name[128] = "";
2045 static char player2Name[128] = "";
2046 static int player1Rating = -1;
2047 static int player2Rating = -1;
2048 /*----------------------------*/
2049
2050 ColorClass curColor = ColorNormal;
2051 int suppressKibitz = 0;
2052
2053 void
2054 read_from_ics(isr, closure, data, count, error)
2055      InputSourceRef isr;
2056      VOIDSTAR closure;
2057      char *data;
2058      int count;
2059      int error;
2060 {
2061 #define BUF_SIZE 8192
2062 #define STARTED_NONE 0
2063 #define STARTED_MOVES 1
2064 #define STARTED_BOARD 2
2065 #define STARTED_OBSERVE 3
2066 #define STARTED_HOLDINGS 4
2067 #define STARTED_CHATTER 5
2068 #define STARTED_COMMENT 6
2069 #define STARTED_MOVES_NOHIDE 7
2070     
2071     static int started = STARTED_NONE;
2072     static char parse[20000];
2073     static int parse_pos = 0;
2074     static char buf[BUF_SIZE + 1];
2075     static int firstTime = TRUE, intfSet = FALSE;
2076     static ColorClass prevColor = ColorNormal;
2077     static int savingComment = FALSE;
2078     char str[500];
2079     int i, oldi;
2080     int buf_len;
2081     int next_out;
2082     int tkind;
2083     int backup;    /* [DM] For zippy color lines */
2084     char *p;
2085
2086     if (appData.debugMode) {
2087       if (!error) {
2088         fprintf(debugFP, "<ICS: ");
2089         show_bytes(debugFP, data, count);
2090         fprintf(debugFP, "\n");
2091       }
2092     }
2093
2094     if (appData.debugMode) { int f = forwardMostMove;
2095         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2096                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2097     }
2098     if (count > 0) {
2099         /* If last read ended with a partial line that we couldn't parse,
2100            prepend it to the new read and try again. */
2101         if (leftover_len > 0) {
2102             for (i=0; i<leftover_len; i++)
2103               buf[i] = buf[leftover_start + i];
2104         }
2105
2106         /* Copy in new characters, removing nulls and \r's */
2107         buf_len = leftover_len;
2108         for (i = 0; i < count; i++) {
2109             if (data[i] != NULLCHAR && data[i] != '\r')
2110               buf[buf_len++] = data[i];
2111             if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && 
2112                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') {
2113                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2114                 buf[buf_len++] = ' '; // replace by space (assumes ICS does not break lines within word)
2115             }
2116         }
2117
2118         buf[buf_len] = NULLCHAR;
2119         next_out = leftover_len;
2120         leftover_start = 0;
2121         
2122         i = 0;
2123         while (i < buf_len) {
2124             /* Deal with part of the TELNET option negotiation
2125                protocol.  We refuse to do anything beyond the
2126                defaults, except that we allow the WILL ECHO option,
2127                which ICS uses to turn off password echoing when we are
2128                directly connected to it.  We reject this option
2129                if localLineEditing mode is on (always on in xboard)
2130                and we are talking to port 23, which might be a real
2131                telnet server that will try to keep WILL ECHO on permanently.
2132              */
2133             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2134                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2135                 unsigned char option;
2136                 oldi = i;
2137                 switch ((unsigned char) buf[++i]) {
2138                   case TN_WILL:
2139                     if (appData.debugMode)
2140                       fprintf(debugFP, "\n<WILL ");
2141                     switch (option = (unsigned char) buf[++i]) {
2142                       case TN_ECHO:
2143                         if (appData.debugMode)
2144                           fprintf(debugFP, "ECHO ");
2145                         /* Reply only if this is a change, according
2146                            to the protocol rules. */
2147                         if (remoteEchoOption) break;
2148                         if (appData.localLineEditing &&
2149                             atoi(appData.icsPort) == TN_PORT) {
2150                             TelnetRequest(TN_DONT, TN_ECHO);
2151                         } else {
2152                             EchoOff();
2153                             TelnetRequest(TN_DO, TN_ECHO);
2154                             remoteEchoOption = TRUE;
2155                         }
2156                         break;
2157                       default:
2158                         if (appData.debugMode)
2159                           fprintf(debugFP, "%d ", option);
2160                         /* Whatever this is, we don't want it. */
2161                         TelnetRequest(TN_DONT, option);
2162                         break;
2163                     }
2164                     break;
2165                   case TN_WONT:
2166                     if (appData.debugMode)
2167                       fprintf(debugFP, "\n<WONT ");
2168                     switch (option = (unsigned char) buf[++i]) {
2169                       case TN_ECHO:
2170                         if (appData.debugMode)
2171                           fprintf(debugFP, "ECHO ");
2172                         /* Reply only if this is a change, according
2173                            to the protocol rules. */
2174                         if (!remoteEchoOption) break;
2175                         EchoOn();
2176                         TelnetRequest(TN_DONT, TN_ECHO);
2177                         remoteEchoOption = FALSE;
2178                         break;
2179                       default:
2180                         if (appData.debugMode)
2181                           fprintf(debugFP, "%d ", (unsigned char) option);
2182                         /* Whatever this is, it must already be turned
2183                            off, because we never agree to turn on
2184                            anything non-default, so according to the
2185                            protocol rules, we don't reply. */
2186                         break;
2187                     }
2188                     break;
2189                   case TN_DO:
2190                     if (appData.debugMode)
2191                       fprintf(debugFP, "\n<DO ");
2192                     switch (option = (unsigned char) buf[++i]) {
2193                       default:
2194                         /* Whatever this is, we refuse to do it. */
2195                         if (appData.debugMode)
2196                           fprintf(debugFP, "%d ", option);
2197                         TelnetRequest(TN_WONT, option);
2198                         break;
2199                     }
2200                     break;
2201                   case TN_DONT:
2202                     if (appData.debugMode)
2203                       fprintf(debugFP, "\n<DONT ");
2204                     switch (option = (unsigned char) buf[++i]) {
2205                       default:
2206                         if (appData.debugMode)
2207                           fprintf(debugFP, "%d ", option);
2208                         /* Whatever this is, we are already not doing
2209                            it, because we never agree to do anything
2210                            non-default, so according to the protocol
2211                            rules, we don't reply. */
2212                         break;
2213                     }
2214                     break;
2215                   case TN_IAC:
2216                     if (appData.debugMode)
2217                       fprintf(debugFP, "\n<IAC ");
2218                     /* Doubled IAC; pass it through */
2219                     i--;
2220                     break;
2221                   default:
2222                     if (appData.debugMode)
2223                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2224                     /* Drop all other telnet commands on the floor */
2225                     break;
2226                 }
2227                 if (oldi > next_out)
2228                   SendToPlayer(&buf[next_out], oldi - next_out);
2229                 if (++i > next_out)
2230                   next_out = i;
2231                 continue;
2232             }
2233                 
2234             /* OK, this at least will *usually* work */
2235             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2236                 loggedOn = TRUE;
2237             }
2238             
2239             if (loggedOn && !intfSet) {
2240                 if (ics_type == ICS_ICC) {
2241                   sprintf(str,
2242                           "/set-quietly interface %s\n/set-quietly style 12\n",
2243                           programVersion);
2244
2245                 } else if (ics_type == ICS_CHESSNET) {
2246                   sprintf(str, "/style 12\n");
2247                 } else {
2248                   strcpy(str, "alias $ @\n$set interface ");
2249                   strcat(str, programVersion);
2250                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2251 #ifdef WIN32
2252                   strcat(str, "$iset nohighlight 1\n");
2253 #endif
2254                   strcat(str, "$iset lock 1\n$style 12\n");
2255                 }
2256                 SendToICS(str);
2257                 intfSet = TRUE;
2258             }
2259
2260             if (started == STARTED_COMMENT) {
2261                 /* Accumulate characters in comment */
2262                 parse[parse_pos++] = buf[i];
2263                 if (buf[i] == '\n') {
2264                     parse[parse_pos] = NULLCHAR;
2265                     if(!suppressKibitz) // [HGM] kibitz
2266                         AppendComment(forwardMostMove, StripHighlight(parse));
2267                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2268                         int nrDigit = 0, nrAlph = 0, i;
2269                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2270                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2271                         parse[parse_pos] = NULLCHAR;
2272                         // try to be smart: if it does not look like search info, it should go to
2273                         // ICS interaction window after all, not to engine-output window.
2274                         for(i=0; i<parse_pos; i++) { // count letters and digits
2275                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2276                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2277                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2278                         }
2279                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2280                             int depth=0; float score;
2281                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2282                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2283                                 pvInfoList[forwardMostMove-1].depth = depth;
2284                                 pvInfoList[forwardMostMove-1].score = 100*score;
2285                             }
2286                             OutputKibitz(suppressKibitz, parse);
2287                         } else {
2288                             char tmp[MSG_SIZ];
2289                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2290                             SendToPlayer(tmp, strlen(tmp));
2291                         }
2292                     }
2293                     started = STARTED_NONE;
2294                 } else {
2295                     /* Don't match patterns against characters in chatter */
2296                     i++;
2297                     continue;
2298                 }
2299             }
2300             if (started == STARTED_CHATTER) {
2301                 if (buf[i] != '\n') {
2302                     /* Don't match patterns against characters in chatter */
2303                     i++;
2304                     continue;
2305                 }
2306                 started = STARTED_NONE;
2307             }
2308
2309             /* Kludge to deal with rcmd protocol */
2310             if (firstTime && looking_at(buf, &i, "\001*")) {
2311                 DisplayFatalError(&buf[1], 0, 1);
2312                 continue;
2313             } else {
2314                 firstTime = FALSE;
2315             }
2316
2317             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2318                 ics_type = ICS_ICC;
2319                 ics_prefix = "/";
2320                 if (appData.debugMode)
2321                   fprintf(debugFP, "ics_type %d\n", ics_type);
2322                 continue;
2323             }
2324             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2325                 ics_type = ICS_FICS;
2326                 ics_prefix = "$";
2327                 if (appData.debugMode)
2328                   fprintf(debugFP, "ics_type %d\n", ics_type);
2329                 continue;
2330             }
2331             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2332                 ics_type = ICS_CHESSNET;
2333                 ics_prefix = "/";
2334                 if (appData.debugMode)
2335                   fprintf(debugFP, "ics_type %d\n", ics_type);
2336                 continue;
2337             }
2338
2339             if (!loggedOn &&
2340                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2341                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2342                  looking_at(buf, &i, "will be \"*\""))) {
2343               strcpy(ics_handle, star_match[0]);
2344               continue;
2345             }
2346
2347             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2348               char buf[MSG_SIZ];
2349               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2350               DisplayIcsInteractionTitle(buf);
2351               have_set_title = TRUE;
2352             }
2353
2354             /* skip finger notes */
2355             if (started == STARTED_NONE &&
2356                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2357                  (buf[i] == '1' && buf[i+1] == '0')) &&
2358                 buf[i+2] == ':' && buf[i+3] == ' ') {
2359               started = STARTED_CHATTER;
2360               i += 3;
2361               continue;
2362             }
2363
2364             /* skip formula vars */
2365             if (started == STARTED_NONE &&
2366                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2367               started = STARTED_CHATTER;
2368               i += 3;
2369               continue;
2370             }
2371
2372             oldi = i;
2373             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2374             if (appData.autoKibitz && started == STARTED_NONE && 
2375                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2376                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2377                 if(looking_at(buf, &i, "* kibitzes: ") &&
2378                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2379                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2380                         suppressKibitz = TRUE;
2381                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2382                                 && (gameMode == IcsPlayingWhite)) ||
2383                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2384                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2385                             started = STARTED_CHATTER; // own kibitz we simply discard
2386                         else {
2387                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2388                             parse_pos = 0; parse[0] = NULLCHAR;
2389                             savingComment = TRUE;
2390                             suppressKibitz = gameMode != IcsObserving ? 2 :
2391                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2392                         } 
2393                         continue;
2394                 } else
2395                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2396                     started = STARTED_CHATTER;
2397                     suppressKibitz = TRUE;
2398                 }
2399             } // [HGM] kibitz: end of patch
2400
2401             if (appData.zippyTalk || appData.zippyPlay) {
2402                 /* [DM] Backup address for color zippy lines */
2403                 backup = i;
2404 #if ZIPPY
2405        #ifdef WIN32
2406                if (loggedOn == TRUE)
2407                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2408                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2409        #else
2410                 if (ZippyControl(buf, &i) ||
2411                     ZippyConverse(buf, &i) ||
2412                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2413                       loggedOn = TRUE;
2414                       if (!appData.colorize) continue;
2415                 }
2416        #endif
2417 #endif
2418             } // [DM] 'else { ' deleted
2419                 if (/* Don't color "message" or "messages" output */
2420                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2421                     looking_at(buf, &i, "*. * at *:*: ") ||
2422                     looking_at(buf, &i, "--* (*:*): ") ||
2423                     /* Regular tells and says */
2424                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2425                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2426                     looking_at(buf, &i, "* says: ") ||
2427                     /* Message notifications (same color as tells) */
2428                     looking_at(buf, &i, "* has left a message ") ||
2429                     looking_at(buf, &i, "* just sent you a message:\n") ||
2430                     /* Whispers and kibitzes */
2431                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2432                     looking_at(buf, &i, "* kibitzes: ") ||
2433                     /* Channel tells */
2434                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2435
2436                   if (tkind == 1 && strchr(star_match[0], ':')) {
2437                       /* Avoid "tells you:" spoofs in channels */
2438                      tkind = 3;
2439                   }
2440                   if (star_match[0][0] == NULLCHAR ||
2441                       strchr(star_match[0], ' ') ||
2442                       (tkind == 3 && strchr(star_match[1], ' '))) {
2443                     /* Reject bogus matches */
2444                     i = oldi;
2445                   } else {
2446                     if (appData.colorize) {
2447                       if (oldi > next_out) {
2448                         SendToPlayer(&buf[next_out], oldi - next_out);
2449                         next_out = oldi;
2450                       }
2451                       switch (tkind) {
2452                       case 1:
2453                         Colorize(ColorTell, FALSE);
2454                         curColor = ColorTell;
2455                         break;
2456                       case 2:
2457                         Colorize(ColorKibitz, FALSE);
2458                         curColor = ColorKibitz;
2459                         break;
2460                       case 3:
2461                         p = strrchr(star_match[1], '(');
2462                         if (p == NULL) {
2463                           p = star_match[1];
2464                         } else {
2465                           p++;
2466                         }
2467                         if (atoi(p) == 1) {
2468                           Colorize(ColorChannel1, FALSE);
2469                           curColor = ColorChannel1;
2470                         } else {
2471                           Colorize(ColorChannel, FALSE);
2472                           curColor = ColorChannel;
2473                         }
2474                         break;
2475                       case 5:
2476                         curColor = ColorNormal;
2477                         break;
2478                       }
2479                     }
2480                     if (started == STARTED_NONE && appData.autoComment &&
2481                         (gameMode == IcsObserving ||
2482                          gameMode == IcsPlayingWhite ||
2483                          gameMode == IcsPlayingBlack)) {
2484                       parse_pos = i - oldi;
2485                       memcpy(parse, &buf[oldi], parse_pos);
2486                       parse[parse_pos] = NULLCHAR;
2487                       started = STARTED_COMMENT;
2488                       savingComment = TRUE;
2489                     } else {
2490                       started = STARTED_CHATTER;
2491                       savingComment = FALSE;
2492                     }
2493                     loggedOn = TRUE;
2494                     continue;
2495                   }
2496                 }
2497
2498                 if (looking_at(buf, &i, "* s-shouts: ") ||
2499                     looking_at(buf, &i, "* c-shouts: ")) {
2500                     if (appData.colorize) {
2501                         if (oldi > next_out) {
2502                             SendToPlayer(&buf[next_out], oldi - next_out);
2503                             next_out = oldi;
2504                         }
2505                         Colorize(ColorSShout, FALSE);
2506                         curColor = ColorSShout;
2507                     }
2508                     loggedOn = TRUE;
2509                     started = STARTED_CHATTER;
2510                     continue;
2511                 }
2512
2513                 if (looking_at(buf, &i, "--->")) {
2514                     loggedOn = TRUE;
2515                     continue;
2516                 }
2517
2518                 if (looking_at(buf, &i, "* shouts: ") ||
2519                     looking_at(buf, &i, "--> ")) {
2520                     if (appData.colorize) {
2521                         if (oldi > next_out) {
2522                             SendToPlayer(&buf[next_out], oldi - next_out);
2523                             next_out = oldi;
2524                         }
2525                         Colorize(ColorShout, FALSE);
2526                         curColor = ColorShout;
2527                     }
2528                     loggedOn = TRUE;
2529                     started = STARTED_CHATTER;
2530                     continue;
2531                 }
2532
2533                 if (looking_at( buf, &i, "Challenge:")) {
2534                     if (appData.colorize) {
2535                         if (oldi > next_out) {
2536                             SendToPlayer(&buf[next_out], oldi - next_out);
2537                             next_out = oldi;
2538                         }
2539                         Colorize(ColorChallenge, FALSE);
2540                         curColor = ColorChallenge;
2541                     }
2542                     loggedOn = TRUE;
2543                     continue;
2544                 }
2545
2546                 if (looking_at(buf, &i, "* offers you") ||
2547                     looking_at(buf, &i, "* offers to be") ||
2548                     looking_at(buf, &i, "* would like to") ||
2549                     looking_at(buf, &i, "* requests to") ||
2550                     looking_at(buf, &i, "Your opponent offers") ||
2551                     looking_at(buf, &i, "Your opponent requests")) {
2552
2553                     if (appData.colorize) {
2554                         if (oldi > next_out) {
2555                             SendToPlayer(&buf[next_out], oldi - next_out);
2556                             next_out = oldi;
2557                         }
2558                         Colorize(ColorRequest, FALSE);
2559                         curColor = ColorRequest;
2560                     }
2561                     continue;
2562                 }
2563
2564                 if (looking_at(buf, &i, "* (*) seeking")) {
2565                     if (appData.colorize) {
2566                         if (oldi > next_out) {
2567                             SendToPlayer(&buf[next_out], oldi - next_out);
2568                             next_out = oldi;
2569                         }
2570                         Colorize(ColorSeek, FALSE);
2571                         curColor = ColorSeek;
2572                     }
2573                     continue;
2574             }
2575
2576             if (looking_at(buf, &i, "\\   ")) {
2577                 if (prevColor != ColorNormal) {
2578                     if (oldi > next_out) {
2579                         SendToPlayer(&buf[next_out], oldi - next_out);
2580                         next_out = oldi;
2581                     }
2582                     Colorize(prevColor, TRUE);
2583                     curColor = prevColor;
2584                 }
2585                 if (savingComment) {
2586                     parse_pos = i - oldi;
2587                     memcpy(parse, &buf[oldi], parse_pos);
2588                     parse[parse_pos] = NULLCHAR;
2589                     started = STARTED_COMMENT;
2590                 } else {
2591                     started = STARTED_CHATTER;
2592                 }
2593                 continue;
2594             }
2595
2596             if (looking_at(buf, &i, "Black Strength :") ||
2597                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2598                 looking_at(buf, &i, "<10>") ||
2599                 looking_at(buf, &i, "#@#")) {
2600                 /* Wrong board style */
2601                 loggedOn = TRUE;
2602                 SendToICS(ics_prefix);
2603                 SendToICS("set style 12\n");
2604                 SendToICS(ics_prefix);
2605                 SendToICS("refresh\n");
2606                 continue;
2607             }
2608             
2609             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2610                 ICSInitScript();
2611                 have_sent_ICS_logon = 1;
2612                 continue;
2613             }
2614               
2615             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2616                 (looking_at(buf, &i, "\n<12> ") ||
2617                  looking_at(buf, &i, "<12> "))) {
2618                 loggedOn = TRUE;
2619                 if (oldi > next_out) {
2620                     SendToPlayer(&buf[next_out], oldi - next_out);
2621                 }
2622                 next_out = i;
2623                 started = STARTED_BOARD;
2624                 parse_pos = 0;
2625                 continue;
2626             }
2627
2628             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2629                 looking_at(buf, &i, "<b1> ")) {
2630                 if (oldi > next_out) {
2631                     SendToPlayer(&buf[next_out], oldi - next_out);
2632                 }
2633                 next_out = i;
2634                 started = STARTED_HOLDINGS;
2635                 parse_pos = 0;
2636                 continue;
2637             }
2638
2639             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2640                 loggedOn = TRUE;
2641                 /* Header for a move list -- first line */
2642
2643                 switch (ics_getting_history) {
2644                   case H_FALSE:
2645                     switch (gameMode) {
2646                       case IcsIdle:
2647                       case BeginningOfGame:
2648                         /* User typed "moves" or "oldmoves" while we
2649                            were idle.  Pretend we asked for these
2650                            moves and soak them up so user can step
2651                            through them and/or save them.
2652                            */
2653                         Reset(FALSE, TRUE);
2654                         gameMode = IcsObserving;
2655                         ModeHighlight();
2656                         ics_gamenum = -1;
2657                         ics_getting_history = H_GOT_UNREQ_HEADER;
2658                         break;
2659                       case EditGame: /*?*/
2660                       case EditPosition: /*?*/
2661                         /* Should above feature work in these modes too? */
2662                         /* For now it doesn't */
2663                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2664                         break;
2665                       default:
2666                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2667                         break;
2668                     }
2669                     break;
2670                   case H_REQUESTED:
2671                     /* Is this the right one? */
2672                     if (gameInfo.white && gameInfo.black &&
2673                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2674                         strcmp(gameInfo.black, star_match[2]) == 0) {
2675                         /* All is well */
2676                         ics_getting_history = H_GOT_REQ_HEADER;
2677                     }
2678                     break;
2679                   case H_GOT_REQ_HEADER:
2680                   case H_GOT_UNREQ_HEADER:
2681                   case H_GOT_UNWANTED_HEADER:
2682                   case H_GETTING_MOVES:
2683                     /* Should not happen */
2684                     DisplayError(_("Error gathering move list: two headers"), 0);
2685                     ics_getting_history = H_FALSE;
2686                     break;
2687                 }
2688
2689                 /* Save player ratings into gameInfo if needed */
2690                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2691                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2692                     (gameInfo.whiteRating == -1 ||
2693                      gameInfo.blackRating == -1)) {
2694
2695                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2696                     gameInfo.blackRating = string_to_rating(star_match[3]);
2697                     if (appData.debugMode)
2698                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2699                               gameInfo.whiteRating, gameInfo.blackRating);
2700                 }
2701                 continue;
2702             }
2703
2704             if (looking_at(buf, &i,
2705               "* * match, initial time: * minute*, increment: * second")) {
2706                 /* Header for a move list -- second line */
2707                 /* Initial board will follow if this is a wild game */
2708                 if (gameInfo.event != NULL) free(gameInfo.event);
2709                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2710                 gameInfo.event = StrSave(str);
2711                 /* [HGM] we switched variant. Translate boards if needed. */
2712                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2713                 continue;
2714             }
2715
2716             if (looking_at(buf, &i, "Move  ")) {
2717                 /* Beginning of a move list */
2718                 switch (ics_getting_history) {
2719                   case H_FALSE:
2720                     /* Normally should not happen */
2721                     /* Maybe user hit reset while we were parsing */
2722                     break;
2723                   case H_REQUESTED:
2724                     /* Happens if we are ignoring a move list that is not
2725                      * the one we just requested.  Common if the user
2726                      * tries to observe two games without turning off
2727                      * getMoveList */
2728                     break;
2729                   case H_GETTING_MOVES:
2730                     /* Should not happen */
2731                     DisplayError(_("Error gathering move list: nested"), 0);
2732                     ics_getting_history = H_FALSE;
2733                     break;
2734                   case H_GOT_REQ_HEADER:
2735                     ics_getting_history = H_GETTING_MOVES;
2736                     started = STARTED_MOVES;
2737                     parse_pos = 0;
2738                     if (oldi > next_out) {
2739                         SendToPlayer(&buf[next_out], oldi - next_out);
2740                     }
2741                     break;
2742                   case H_GOT_UNREQ_HEADER:
2743                     ics_getting_history = H_GETTING_MOVES;
2744                     started = STARTED_MOVES_NOHIDE;
2745                     parse_pos = 0;
2746                     break;
2747                   case H_GOT_UNWANTED_HEADER:
2748                     ics_getting_history = H_FALSE;
2749                     break;
2750                 }
2751                 continue;
2752             }                           
2753             
2754             if (looking_at(buf, &i, "% ") ||
2755                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2756                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2757                 savingComment = FALSE;
2758                 switch (started) {
2759                   case STARTED_MOVES:
2760                   case STARTED_MOVES_NOHIDE:
2761                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2762                     parse[parse_pos + i - oldi] = NULLCHAR;
2763                     ParseGameHistory(parse);
2764 #if ZIPPY
2765                     if (appData.zippyPlay && first.initDone) {
2766                         FeedMovesToProgram(&first, forwardMostMove);
2767                         if (gameMode == IcsPlayingWhite) {
2768                             if (WhiteOnMove(forwardMostMove)) {
2769                                 if (first.sendTime) {
2770                                   if (first.useColors) {
2771                                     SendToProgram("black\n", &first); 
2772                                   }
2773                                   SendTimeRemaining(&first, TRUE);
2774                                 }
2775 #if 0
2776                                 if (first.useColors) {
2777                                   SendToProgram("white\ngo\n", &first);
2778                                 } else {
2779                                   SendToProgram("go\n", &first);
2780                                 }
2781 #else
2782                                 if (first.useColors) {
2783                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2784                                 }
2785                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2786 #endif
2787                                 first.maybeThinking = TRUE;
2788                             } else {
2789                                 if (first.usePlayother) {
2790                                   if (first.sendTime) {
2791                                     SendTimeRemaining(&first, TRUE);
2792                                   }
2793                                   SendToProgram("playother\n", &first);
2794                                   firstMove = FALSE;
2795                                 } else {
2796                                   firstMove = TRUE;
2797                                 }
2798                             }
2799                         } else if (gameMode == IcsPlayingBlack) {
2800                             if (!WhiteOnMove(forwardMostMove)) {
2801                                 if (first.sendTime) {
2802                                   if (first.useColors) {
2803                                     SendToProgram("white\n", &first);
2804                                   }
2805                                   SendTimeRemaining(&first, FALSE);
2806                                 }
2807 #if 0
2808                                 if (first.useColors) {
2809                                   SendToProgram("black\ngo\n", &first);
2810                                 } else {
2811                                   SendToProgram("go\n", &first);
2812                                 }
2813 #else
2814                                 if (first.useColors) {
2815                                   SendToProgram("black\n", &first);
2816                                 }
2817                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2818 #endif
2819                                 first.maybeThinking = TRUE;
2820                             } else {
2821                                 if (first.usePlayother) {
2822                                   if (first.sendTime) {
2823                                     SendTimeRemaining(&first, FALSE);
2824                                   }
2825                                   SendToProgram("playother\n", &first);
2826                                   firstMove = FALSE;
2827                                 } else {
2828                                   firstMove = TRUE;
2829                                 }
2830                             }
2831                         }                       
2832                     }
2833 #endif
2834                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2835                         /* Moves came from oldmoves or moves command
2836                            while we weren't doing anything else.
2837                            */
2838                         currentMove = forwardMostMove;
2839                         ClearHighlights();/*!!could figure this out*/
2840                         flipView = appData.flipView;
2841                         DrawPosition(FALSE, boards[currentMove]);
2842                         DisplayBothClocks();
2843                         sprintf(str, "%s vs. %s",
2844                                 gameInfo.white, gameInfo.black);
2845                         DisplayTitle(str);
2846                         gameMode = IcsIdle;
2847                     } else {
2848                         /* Moves were history of an active game */
2849                         if (gameInfo.resultDetails != NULL) {
2850                             free(gameInfo.resultDetails);
2851                             gameInfo.resultDetails = NULL;
2852                         }
2853                     }
2854                     HistorySet(parseList, backwardMostMove,
2855                                forwardMostMove, currentMove-1);
2856                     DisplayMove(currentMove - 1);
2857                     if (started == STARTED_MOVES) next_out = i;
2858                     started = STARTED_NONE;
2859                     ics_getting_history = H_FALSE;
2860                     break;
2861
2862                   case STARTED_OBSERVE:
2863                     started = STARTED_NONE;
2864                     SendToICS(ics_prefix);
2865                     SendToICS("refresh\n");
2866                     break;
2867
2868                   default:
2869                     break;
2870                 }
2871                 if(bookHit) { // [HGM] book: simulate book reply
2872                     static char bookMove[MSG_SIZ]; // a bit generous?
2873
2874                     programStats.nodes = programStats.depth = programStats.time = 
2875                     programStats.score = programStats.got_only_move = 0;
2876                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2877
2878                     strcpy(bookMove, "move ");
2879                     strcat(bookMove, bookHit);
2880                     HandleMachineMove(bookMove, &first);
2881                 }
2882                 continue;
2883             }
2884             
2885             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2886                  started == STARTED_HOLDINGS ||
2887                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2888                 /* Accumulate characters in move list or board */
2889                 parse[parse_pos++] = buf[i];
2890             }
2891             
2892             /* Start of game messages.  Mostly we detect start of game
2893                when the first board image arrives.  On some versions
2894                of the ICS, though, we need to do a "refresh" after starting
2895                to observe in order to get the current board right away. */
2896             if (looking_at(buf, &i, "Adding game * to observation list")) {
2897                 started = STARTED_OBSERVE;
2898                 continue;
2899             }
2900
2901             /* Handle auto-observe */
2902             if (appData.autoObserve &&
2903                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2904                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2905                 char *player;
2906                 /* Choose the player that was highlighted, if any. */
2907                 if (star_match[0][0] == '\033' ||
2908                     star_match[1][0] != '\033') {
2909                     player = star_match[0];
2910                 } else {
2911                     player = star_match[2];
2912                 }
2913                 sprintf(str, "%sobserve %s\n",
2914                         ics_prefix, StripHighlightAndTitle(player));
2915                 SendToICS(str);
2916
2917                 /* Save ratings from notify string */
2918                 strcpy(player1Name, star_match[0]);
2919                 player1Rating = string_to_rating(star_match[1]);
2920                 strcpy(player2Name, star_match[2]);
2921                 player2Rating = string_to_rating(star_match[3]);
2922
2923                 if (appData.debugMode)
2924                   fprintf(debugFP, 
2925                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2926                           player1Name, player1Rating,
2927                           player2Name, player2Rating);
2928
2929                 continue;
2930             }
2931
2932             /* Deal with automatic examine mode after a game,
2933                and with IcsObserving -> IcsExamining transition */
2934             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2935                 looking_at(buf, &i, "has made you an examiner of game *")) {
2936
2937                 int gamenum = atoi(star_match[0]);
2938                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2939                     gamenum == ics_gamenum) {
2940                     /* We were already playing or observing this game;
2941                        no need to refetch history */
2942                     gameMode = IcsExamining;
2943                     if (pausing) {
2944                         pauseExamForwardMostMove = forwardMostMove;
2945                     } else if (currentMove < forwardMostMove) {
2946                         ForwardInner(forwardMostMove);
2947                     }
2948                 } else {
2949                     /* I don't think this case really can happen */
2950                     SendToICS(ics_prefix);
2951                     SendToICS("refresh\n");
2952                 }
2953                 continue;
2954             }    
2955             
2956             /* Error messages */
2957 //          if (ics_user_moved) {
2958             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2959                 if (looking_at(buf, &i, "Illegal move") ||
2960                     looking_at(buf, &i, "Not a legal move") ||
2961                     looking_at(buf, &i, "Your king is in check") ||
2962                     looking_at(buf, &i, "It isn't your turn") ||
2963                     looking_at(buf, &i, "It is not your move")) {
2964                     /* Illegal move */
2965                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2966                         currentMove = --forwardMostMove;
2967                         DisplayMove(currentMove - 1); /* before DMError */
2968                         DrawPosition(FALSE, boards[currentMove]);
2969                         SwitchClocks();
2970                         DisplayBothClocks();
2971                     }
2972                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2973                     ics_user_moved = 0;
2974                     continue;
2975                 }
2976             }
2977
2978             if (looking_at(buf, &i, "still have time") ||
2979                 looking_at(buf, &i, "not out of time") ||
2980                 looking_at(buf, &i, "either player is out of time") ||
2981                 looking_at(buf, &i, "has timeseal; checking")) {
2982                 /* We must have called his flag a little too soon */
2983                 whiteFlag = blackFlag = FALSE;
2984                 continue;
2985             }
2986
2987             if (looking_at(buf, &i, "added * seconds to") ||
2988                 looking_at(buf, &i, "seconds were added to")) {
2989                 /* Update the clocks */
2990                 SendToICS(ics_prefix);
2991                 SendToICS("refresh\n");
2992                 continue;
2993             }
2994
2995             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2996                 ics_clock_paused = TRUE;
2997                 StopClocks();
2998                 continue;
2999             }
3000
3001             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3002                 ics_clock_paused = FALSE;
3003                 StartClocks();
3004                 continue;
3005             }
3006
3007             /* Grab player ratings from the Creating: message.
3008                Note we have to check for the special case when
3009                the ICS inserts things like [white] or [black]. */
3010             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3011                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3012                 /* star_matches:
3013                    0    player 1 name (not necessarily white)
3014                    1    player 1 rating
3015                    2    empty, white, or black (IGNORED)
3016                    3    player 2 name (not necessarily black)
3017                    4    player 2 rating
3018                    
3019                    The names/ratings are sorted out when the game
3020                    actually starts (below).
3021                 */
3022                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3023                 player1Rating = string_to_rating(star_match[1]);
3024                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3025                 player2Rating = string_to_rating(star_match[4]);
3026
3027                 if (appData.debugMode)
3028                   fprintf(debugFP, 
3029                           "Ratings from 'Creating:' %s %d, %s %d\n",
3030                           player1Name, player1Rating,
3031                           player2Name, player2Rating);
3032
3033                 continue;
3034             }
3035             
3036             /* Improved generic start/end-of-game messages */
3037             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3038                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3039                 /* If tkind == 0: */
3040                 /* star_match[0] is the game number */
3041                 /*           [1] is the white player's name */
3042                 /*           [2] is the black player's name */
3043                 /* For end-of-game: */
3044                 /*           [3] is the reason for the game end */
3045                 /*           [4] is a PGN end game-token, preceded by " " */
3046                 /* For start-of-game: */
3047                 /*           [3] begins with "Creating" or "Continuing" */
3048                 /*           [4] is " *" or empty (don't care). */
3049                 int gamenum = atoi(star_match[0]);
3050                 char *whitename, *blackname, *why, *endtoken;
3051                 ChessMove endtype = (ChessMove) 0;
3052
3053                 if (tkind == 0) {
3054                   whitename = star_match[1];
3055                   blackname = star_match[2];
3056                   why = star_match[3];
3057                   endtoken = star_match[4];
3058                 } else {
3059                   whitename = star_match[1];
3060                   blackname = star_match[3];
3061                   why = star_match[5];
3062                   endtoken = star_match[6];
3063                 }
3064
3065                 /* Game start messages */
3066                 if (strncmp(why, "Creating ", 9) == 0 ||
3067                     strncmp(why, "Continuing ", 11) == 0) {
3068                     gs_gamenum = gamenum;
3069                     strcpy(gs_kind, strchr(why, ' ') + 1);
3070 #if ZIPPY
3071                     if (appData.zippyPlay) {
3072                         ZippyGameStart(whitename, blackname);
3073                     }
3074 #endif /*ZIPPY*/
3075                     continue;
3076                 }
3077
3078                 /* Game end messages */
3079                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3080                     ics_gamenum != gamenum) {
3081                     continue;
3082                 }
3083                 while (endtoken[0] == ' ') endtoken++;
3084                 switch (endtoken[0]) {
3085                   case '*':
3086                   default:
3087                     endtype = GameUnfinished;
3088                     break;
3089                   case '0':
3090                     endtype = BlackWins;
3091                     break;
3092                   case '1':
3093                     if (endtoken[1] == '/')
3094                       endtype = GameIsDrawn;
3095                     else
3096                       endtype = WhiteWins;
3097                     break;
3098                 }
3099                 GameEnds(endtype, why, GE_ICS);
3100 #if ZIPPY
3101                 if (appData.zippyPlay && first.initDone) {
3102                     ZippyGameEnd(endtype, why);
3103                     if (first.pr == NULL) {
3104                       /* Start the next process early so that we'll
3105                          be ready for the next challenge */
3106                       StartChessProgram(&first);
3107                     }
3108                     /* Send "new" early, in case this command takes
3109                        a long time to finish, so that we'll be ready
3110                        for the next challenge. */
3111                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3112                     Reset(TRUE, TRUE);
3113                 }
3114 #endif /*ZIPPY*/
3115                 continue;
3116             }
3117
3118             if (looking_at(buf, &i, "Removing game * from observation") ||
3119                 looking_at(buf, &i, "no longer observing game *") ||
3120                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3121                 if (gameMode == IcsObserving &&
3122                     atoi(star_match[0]) == ics_gamenum)
3123                   {
3124                       /* icsEngineAnalyze */
3125                       if (appData.icsEngineAnalyze) {
3126                             ExitAnalyzeMode();
3127                             ModeHighlight();
3128                       }
3129                       StopClocks();
3130                       gameMode = IcsIdle;
3131                       ics_gamenum = -1;
3132                       ics_user_moved = FALSE;
3133                   }
3134                 continue;
3135             }
3136
3137             if (looking_at(buf, &i, "no longer examining game *")) {
3138                 if (gameMode == IcsExamining &&
3139                     atoi(star_match[0]) == ics_gamenum)
3140                   {
3141                       gameMode = IcsIdle;
3142                       ics_gamenum = -1;
3143                       ics_user_moved = FALSE;
3144                   }
3145                 continue;
3146             }
3147
3148             /* Advance leftover_start past any newlines we find,
3149                so only partial lines can get reparsed */
3150             if (looking_at(buf, &i, "\n")) {
3151                 prevColor = curColor;
3152                 if (curColor != ColorNormal) {
3153                     if (oldi > next_out) {
3154                         SendToPlayer(&buf[next_out], oldi - next_out);
3155                         next_out = oldi;
3156                     }
3157                     Colorize(ColorNormal, FALSE);
3158                     curColor = ColorNormal;
3159                 }
3160                 if (started == STARTED_BOARD) {
3161                     started = STARTED_NONE;
3162                     parse[parse_pos] = NULLCHAR;
3163                     ParseBoard12(parse);
3164                     ics_user_moved = 0;
3165
3166                     /* Send premove here */
3167                     if (appData.premove) {
3168                       char str[MSG_SIZ];
3169                       if (currentMove == 0 &&
3170                           gameMode == IcsPlayingWhite &&
3171                           appData.premoveWhite) {
3172                         sprintf(str, "%s%s\n", ics_prefix,
3173                                 appData.premoveWhiteText);
3174                         if (appData.debugMode)
3175                           fprintf(debugFP, "Sending premove:\n");
3176                         SendToICS(str);
3177                       } else if (currentMove == 1 &&
3178                                  gameMode == IcsPlayingBlack &&
3179                                  appData.premoveBlack) {
3180                         sprintf(str, "%s%s\n", ics_prefix,
3181                                 appData.premoveBlackText);
3182                         if (appData.debugMode)
3183                           fprintf(debugFP, "Sending premove:\n");
3184                         SendToICS(str);
3185                       } else if (gotPremove) {
3186                         gotPremove = 0;
3187                         ClearPremoveHighlights();
3188                         if (appData.debugMode)
3189                           fprintf(debugFP, "Sending premove:\n");
3190                           UserMoveEvent(premoveFromX, premoveFromY, 
3191                                         premoveToX, premoveToY, 
3192                                         premovePromoChar);
3193                       }
3194                     }
3195
3196                     /* Usually suppress following prompt */
3197                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3198                         if (looking_at(buf, &i, "*% ")) {
3199                             savingComment = FALSE;
3200                         }
3201                     }
3202                     next_out = i;
3203                 } else if (started == STARTED_HOLDINGS) {
3204                     int gamenum;
3205                     char new_piece[MSG_SIZ];
3206                     started = STARTED_NONE;
3207                     parse[parse_pos] = NULLCHAR;
3208                     if (appData.debugMode)
3209                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3210                                                         parse, currentMove);
3211                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3212                         gamenum == ics_gamenum) {
3213                         if (gameInfo.variant == VariantNormal) {
3214                           /* [HGM] We seem to switch variant during a game!
3215                            * Presumably no holdings were displayed, so we have
3216                            * to move the position two files to the right to
3217                            * create room for them!
3218                            */
3219                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3220                           /* Get a move list just to see the header, which
3221                              will tell us whether this is really bug or zh */
3222                           if (ics_getting_history == H_FALSE) {
3223                             ics_getting_history = H_REQUESTED;
3224                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3225                             SendToICS(str);
3226                           }
3227                         }
3228                         new_piece[0] = NULLCHAR;
3229                         sscanf(parse, "game %d white [%s black [%s <- %s",
3230                                &gamenum, white_holding, black_holding,
3231                                new_piece);
3232                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3233                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3234                         /* [HGM] copy holdings to board holdings area */
3235                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3236                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3237 #if ZIPPY
3238                         if (appData.zippyPlay && first.initDone) {
3239                             ZippyHoldings(white_holding, black_holding,
3240                                           new_piece);
3241                         }
3242 #endif /*ZIPPY*/
3243                         if (tinyLayout || smallLayout) {
3244                             char wh[16], bh[16];
3245                             PackHolding(wh, white_holding);
3246                             PackHolding(bh, black_holding);
3247                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3248                                     gameInfo.white, gameInfo.black);
3249                         } else {
3250                             sprintf(str, "%s [%s] vs. %s [%s]",
3251                                     gameInfo.white, white_holding,
3252                                     gameInfo.black, black_holding);
3253                         }
3254
3255                         DrawPosition(FALSE, boards[currentMove]);
3256                         DisplayTitle(str);
3257                     }
3258                     /* Suppress following prompt */
3259                     if (looking_at(buf, &i, "*% ")) {
3260                         savingComment = FALSE;
3261                     }
3262                     next_out = i;
3263                 }
3264                 continue;
3265             }
3266
3267             i++;                /* skip unparsed character and loop back */
3268         }
3269         
3270         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3271             started != STARTED_HOLDINGS && i > next_out) {
3272             SendToPlayer(&buf[next_out], i - next_out);
3273             next_out = i;
3274         }
3275         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3276         
3277         leftover_len = buf_len - leftover_start;
3278         /* if buffer ends with something we couldn't parse,
3279            reparse it after appending the next read */
3280         
3281     } else if (count == 0) {
3282         RemoveInputSource(isr);
3283         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3284     } else {
3285         DisplayFatalError(_("Error reading from ICS"), error, 1);
3286     }
3287 }
3288
3289
3290 /* Board style 12 looks like this:
3291    
3292    <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
3293    
3294  * The "<12> " is stripped before it gets to this routine.  The two
3295  * trailing 0's (flip state and clock ticking) are later addition, and
3296  * some chess servers may not have them, or may have only the first.
3297  * Additional trailing fields may be added in the future.  
3298  */
3299
3300 #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"
3301
3302 #define RELATION_OBSERVING_PLAYED    0
3303 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3304 #define RELATION_PLAYING_MYMOVE      1
3305 #define RELATION_PLAYING_NOTMYMOVE  -1
3306 #define RELATION_EXAMINING           2
3307 #define RELATION_ISOLATED_BOARD     -3
3308 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3309
3310 void
3311 ParseBoard12(string)
3312      char *string;
3313
3314     GameMode newGameMode;
3315     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3316     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3317     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3318     char to_play, board_chars[200];
3319     char move_str[500], str[500], elapsed_time[500];
3320     char black[32], white[32];
3321     Board board;
3322     int prevMove = currentMove;
3323     int ticking = 2;
3324     ChessMove moveType;
3325     int fromX, fromY, toX, toY;
3326     char promoChar;
3327     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3328     char *bookHit = NULL; // [HGM] book
3329
3330     fromX = fromY = toX = toY = -1;
3331     
3332     newGame = FALSE;
3333
3334     if (appData.debugMode)
3335       fprintf(debugFP, _("Parsing board: %s\n"), string);
3336
3337     move_str[0] = NULLCHAR;
3338     elapsed_time[0] = NULLCHAR;
3339     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3340         int  i = 0, j;
3341         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3342             if(string[i] == ' ') { ranks++; files = 0; }
3343             else files++;
3344             i++;
3345         }
3346         for(j = 0; j <i; j++) board_chars[j] = string[j];
3347         board_chars[i] = '\0';
3348         string += i + 1;
3349     }
3350     n = sscanf(string, PATTERN, &to_play, &double_push,
3351                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3352                &gamenum, white, black, &relation, &basetime, &increment,
3353                &white_stren, &black_stren, &white_time, &black_time,
3354                &moveNum, str, elapsed_time, move_str, &ics_flip,
3355                &ticking);
3356
3357     if (n < 21) {
3358         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3359         DisplayError(str, 0);
3360         return;
3361     }
3362
3363     /* Convert the move number to internal form */
3364     moveNum = (moveNum - 1) * 2;
3365     if (to_play == 'B') moveNum++;
3366     if (moveNum >= MAX_MOVES) {
3367       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3368                         0, 1);
3369       return;
3370     }
3371     
3372     switch (relation) {
3373       case RELATION_OBSERVING_PLAYED:
3374       case RELATION_OBSERVING_STATIC:
3375         if (gamenum == -1) {
3376             /* Old ICC buglet */
3377             relation = RELATION_OBSERVING_STATIC;
3378         }
3379         newGameMode = IcsObserving;
3380         break;
3381       case RELATION_PLAYING_MYMOVE:
3382       case RELATION_PLAYING_NOTMYMOVE:
3383         newGameMode =
3384           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3385             IcsPlayingWhite : IcsPlayingBlack;
3386         break;
3387       case RELATION_EXAMINING:
3388         newGameMode = IcsExamining;
3389         break;
3390       case RELATION_ISOLATED_BOARD:
3391       default:
3392         /* Just display this board.  If user was doing something else,
3393            we will forget about it until the next board comes. */ 
3394         newGameMode = IcsIdle;
3395         break;
3396       case RELATION_STARTING_POSITION:
3397         newGameMode = gameMode;
3398         break;
3399     }
3400     
3401     /* Modify behavior for initial board display on move listing
3402        of wild games.
3403        */
3404     switch (ics_getting_history) {
3405       case H_FALSE:
3406       case H_REQUESTED:
3407         break;
3408       case H_GOT_REQ_HEADER:
3409       case H_GOT_UNREQ_HEADER:
3410         /* This is the initial position of the current game */
3411         gamenum = ics_gamenum;
3412         moveNum = 0;            /* old ICS bug workaround */
3413         if (to_play == 'B') {
3414           startedFromSetupPosition = TRUE;
3415           blackPlaysFirst = TRUE;
3416           moveNum = 1;
3417           if (forwardMostMove == 0) forwardMostMove = 1;
3418           if (backwardMostMove == 0) backwardMostMove = 1;
3419           if (currentMove == 0) currentMove = 1;
3420         }
3421         newGameMode = gameMode;
3422         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3423         break;
3424       case H_GOT_UNWANTED_HEADER:
3425         /* This is an initial board that we don't want */
3426         return;
3427       case H_GETTING_MOVES:
3428         /* Should not happen */
3429         DisplayError(_("Error gathering move list: extra board"), 0);
3430         ics_getting_history = H_FALSE;
3431         return;
3432     }
3433     
3434     /* Take action if this is the first board of a new game, or of a
3435        different game than is currently being displayed.  */
3436     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3437         relation == RELATION_ISOLATED_BOARD) {
3438         
3439         /* Forget the old game and get the history (if any) of the new one */
3440         if (gameMode != BeginningOfGame) {
3441           Reset(FALSE, TRUE);
3442         }
3443         newGame = TRUE;
3444         if (appData.autoRaiseBoard) BoardToTop();
3445         prevMove = -3;
3446         if (gamenum == -1) {
3447             newGameMode = IcsIdle;
3448         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3449                    appData.getMoveList) {
3450             /* Need to get game history */
3451             ics_getting_history = H_REQUESTED;
3452             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3453             SendToICS(str);
3454         }
3455         
3456         /* Initially flip the board to have black on the bottom if playing
3457            black or if the ICS flip flag is set, but let the user change
3458            it with the Flip View button. */
3459         flipView = appData.autoFlipView ? 
3460           (newGameMode == IcsPlayingBlack) || ics_flip :
3461           appData.flipView;
3462         
3463         /* Done with values from previous mode; copy in new ones */
3464         gameMode = newGameMode;
3465         ModeHighlight();
3466         ics_gamenum = gamenum;
3467         if (gamenum == gs_gamenum) {
3468             int klen = strlen(gs_kind);
3469             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3470             sprintf(str, "ICS %s", gs_kind);
3471             gameInfo.event = StrSave(str);
3472         } else {
3473             gameInfo.event = StrSave("ICS game");
3474         }
3475         gameInfo.site = StrSave(appData.icsHost);
3476         gameInfo.date = PGNDate();
3477         gameInfo.round = StrSave("-");
3478         gameInfo.white = StrSave(white);
3479         gameInfo.black = StrSave(black);
3480         timeControl = basetime * 60 * 1000;
3481         timeControl_2 = 0;
3482         timeIncrement = increment * 1000;
3483         movesPerSession = 0;
3484         gameInfo.timeControl = TimeControlTagValue();
3485         VariantSwitch(board, StringToVariant(gameInfo.event) );
3486   if (appData.debugMode) {
3487     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3488     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3489     setbuf(debugFP, NULL);
3490   }
3491
3492         gameInfo.outOfBook = NULL;
3493         
3494         /* Do we have the ratings? */
3495         if (strcmp(player1Name, white) == 0 &&
3496             strcmp(player2Name, black) == 0) {
3497             if (appData.debugMode)
3498               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3499                       player1Rating, player2Rating);
3500             gameInfo.whiteRating = player1Rating;
3501             gameInfo.blackRating = player2Rating;
3502         } else if (strcmp(player2Name, white) == 0 &&
3503                    strcmp(player1Name, black) == 0) {
3504             if (appData.debugMode)
3505               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3506                       player2Rating, player1Rating);
3507             gameInfo.whiteRating = player2Rating;
3508             gameInfo.blackRating = player1Rating;
3509         }
3510         player1Name[0] = player2Name[0] = NULLCHAR;
3511
3512         /* Silence shouts if requested */
3513         if (appData.quietPlay &&
3514             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3515             SendToICS(ics_prefix);
3516             SendToICS("set shout 0\n");
3517         }
3518     }
3519     
3520     /* Deal with midgame name changes */
3521     if (!newGame) {
3522         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3523             if (gameInfo.white) free(gameInfo.white);
3524             gameInfo.white = StrSave(white);
3525         }
3526         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3527             if (gameInfo.black) free(gameInfo.black);
3528             gameInfo.black = StrSave(black);
3529         }
3530     }
3531     
3532     /* Throw away game result if anything actually changes in examine mode */
3533     if (gameMode == IcsExamining && !newGame) {
3534         gameInfo.result = GameUnfinished;
3535         if (gameInfo.resultDetails != NULL) {
3536             free(gameInfo.resultDetails);
3537             gameInfo.resultDetails = NULL;
3538         }
3539     }
3540     
3541     /* In pausing && IcsExamining mode, we ignore boards coming
3542        in if they are in a different variation than we are. */
3543     if (pauseExamInvalid) return;
3544     if (pausing && gameMode == IcsExamining) {
3545         if (moveNum <= pauseExamForwardMostMove) {
3546             pauseExamInvalid = TRUE;
3547             forwardMostMove = pauseExamForwardMostMove;
3548             return;
3549         }
3550     }
3551     
3552   if (appData.debugMode) {
3553     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3554   }
3555     /* Parse the board */
3556     for (k = 0; k < ranks; k++) {
3557       for (j = 0; j < files; j++)
3558         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3559       if(gameInfo.holdingsWidth > 1) {
3560            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3561            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3562       }
3563     }
3564     CopyBoard(boards[moveNum], board);
3565     if (moveNum == 0) {
3566         startedFromSetupPosition =
3567           !CompareBoards(board, initialPosition);
3568         if(startedFromSetupPosition)
3569             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3570     }
3571
3572     /* [HGM] Set castling rights. Take the outermost Rooks,
3573        to make it also work for FRC opening positions. Note that board12
3574        is really defective for later FRC positions, as it has no way to
3575        indicate which Rook can castle if they are on the same side of King.
3576        For the initial position we grant rights to the outermost Rooks,
3577        and remember thos rights, and we then copy them on positions
3578        later in an FRC game. This means WB might not recognize castlings with
3579        Rooks that have moved back to their original position as illegal,
3580        but in ICS mode that is not its job anyway.
3581     */
3582     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3583     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3584
3585         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3586             if(board[0][i] == WhiteRook) j = i;
3587         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3588         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3589             if(board[0][i] == WhiteRook) j = i;
3590         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3591         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3592             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3593         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3594         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3595             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3596         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3597
3598         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3599         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3600             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3601         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3602             if(board[BOARD_HEIGHT-1][k] == bKing)
3603                 initialRights[5] = castlingRights[moveNum][5] = k;
3604     } else { int r;
3605         r = castlingRights[moveNum][0] = initialRights[0];
3606         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3607         r = castlingRights[moveNum][1] = initialRights[1];
3608         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3609         r = castlingRights[moveNum][3] = initialRights[3];
3610         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3611         r = castlingRights[moveNum][4] = initialRights[4];
3612         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3613         /* wildcastle kludge: always assume King has rights */
3614         r = castlingRights[moveNum][2] = initialRights[2];
3615         r = castlingRights[moveNum][5] = initialRights[5];
3616     }
3617     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3618     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3619
3620     
3621     if (ics_getting_history == H_GOT_REQ_HEADER ||
3622         ics_getting_history == H_GOT_UNREQ_HEADER) {
3623         /* This was an initial position from a move list, not
3624            the current position */
3625         return;
3626     }
3627     
3628     /* Update currentMove and known move number limits */
3629     newMove = newGame || moveNum > forwardMostMove;
3630
3631     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3632     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3633         takeback = forwardMostMove - moveNum;
3634         for (i = 0; i < takeback; i++) {
3635              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3636              SendToProgram("undo\n", &first);
3637         }
3638     }
3639
3640     if (newGame) {
3641         forwardMostMove = backwardMostMove = currentMove = moveNum;
3642         if (gameMode == IcsExamining && moveNum == 0) {
3643           /* Workaround for ICS limitation: we are not told the wild
3644              type when starting to examine a game.  But if we ask for
3645              the move list, the move list header will tell us */
3646             ics_getting_history = H_REQUESTED;
3647             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3648             SendToICS(str);
3649         }
3650     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3651                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3652         forwardMostMove = moveNum;
3653         if (!pausing || currentMove > forwardMostMove)
3654           currentMove = forwardMostMove;
3655     } else {
3656         /* New part of history that is not contiguous with old part */ 
3657         if (pausing && gameMode == IcsExamining) {
3658             pauseExamInvalid = TRUE;
3659             forwardMostMove = pauseExamForwardMostMove;
3660             return;
3661         }
3662         forwardMostMove = backwardMostMove = currentMove = moveNum;
3663         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3664             ics_getting_history = H_REQUESTED;
3665             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3666             SendToICS(str);
3667         }
3668     }
3669     
3670     /* Update the clocks */
3671     if (strchr(elapsed_time, '.')) {
3672       /* Time is in ms */
3673       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3674       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3675     } else {
3676       /* Time is in seconds */
3677       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3678       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3679     }
3680       
3681
3682 #if ZIPPY
3683     if (appData.zippyPlay && newGame &&
3684         gameMode != IcsObserving && gameMode != IcsIdle &&
3685         gameMode != IcsExamining)
3686       ZippyFirstBoard(moveNum, basetime, increment);
3687 #endif
3688     
3689     /* Put the move on the move list, first converting
3690        to canonical algebraic form. */
3691     if (moveNum > 0) {
3692   if (appData.debugMode) {
3693     if (appData.debugMode) { int f = forwardMostMove;
3694         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3695                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3696     }
3697     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3698     fprintf(debugFP, "moveNum = %d\n", moveNum);
3699     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3700     setbuf(debugFP, NULL);
3701   }
3702         if (moveNum <= backwardMostMove) {
3703             /* We don't know what the board looked like before
3704                this move.  Punt. */
3705             strcpy(parseList[moveNum - 1], move_str);
3706             strcat(parseList[moveNum - 1], " ");
3707             strcat(parseList[moveNum - 1], elapsed_time);
3708             moveList[moveNum - 1][0] = NULLCHAR;
3709         } else if (strcmp(move_str, "none") == 0) {
3710             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3711             /* Again, we don't know what the board looked like;
3712                this is really the start of the game. */
3713             parseList[moveNum - 1][0] = NULLCHAR;
3714             moveList[moveNum - 1][0] = NULLCHAR;
3715             backwardMostMove = moveNum;
3716             startedFromSetupPosition = TRUE;
3717             fromX = fromY = toX = toY = -1;
3718         } else {
3719           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3720           //                 So we parse the long-algebraic move string in stead of the SAN move
3721           int valid; char buf[MSG_SIZ], *prom;
3722
3723           // str looks something like "Q/a1-a2"; kill the slash
3724           if(str[1] == '/') 
3725                 sprintf(buf, "%c%s", str[0], str+2);
3726           else  strcpy(buf, str); // might be castling
3727           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3728                 strcat(buf, prom); // long move lacks promo specification!
3729           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3730                 if(appData.debugMode) 
3731                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3732                 strcpy(move_str, buf);
3733           }
3734           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3735                                 &fromX, &fromY, &toX, &toY, &promoChar)
3736                || ParseOneMove(buf, moveNum - 1, &moveType,
3737                                 &fromX, &fromY, &toX, &toY, &promoChar);
3738           // end of long SAN patch
3739           if (valid) {
3740             (void) CoordsToAlgebraic(boards[moveNum - 1],
3741                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3742                                      fromY, fromX, toY, toX, promoChar,
3743                                      parseList[moveNum-1]);
3744             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3745                              castlingRights[moveNum]) ) {
3746               case MT_NONE:
3747               case MT_STALEMATE:
3748               default:
3749                 break;
3750               case MT_CHECK:
3751                 if(gameInfo.variant != VariantShogi)
3752                     strcat(parseList[moveNum - 1], "+");
3753                 break;
3754               case MT_CHECKMATE:
3755               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3756                 strcat(parseList[moveNum - 1], "#");
3757                 break;
3758             }
3759             strcat(parseList[moveNum - 1], " ");
3760             strcat(parseList[moveNum - 1], elapsed_time);
3761             /* currentMoveString is set as a side-effect of ParseOneMove */
3762             strcpy(moveList[moveNum - 1], currentMoveString);
3763             strcat(moveList[moveNum - 1], "\n");
3764           } else {
3765             /* Move from ICS was illegal!?  Punt. */
3766   if (appData.debugMode) {
3767     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3768     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3769   }
3770 #if 0
3771             if (appData.testLegality && appData.debugMode) {
3772                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3773                 DisplayError(str, 0);
3774             }
3775 #endif
3776             strcpy(parseList[moveNum - 1], move_str);
3777             strcat(parseList[moveNum - 1], " ");
3778             strcat(parseList[moveNum - 1], elapsed_time);
3779             moveList[moveNum - 1][0] = NULLCHAR;
3780             fromX = fromY = toX = toY = -1;
3781           }
3782         }
3783   if (appData.debugMode) {
3784     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3785     setbuf(debugFP, NULL);
3786   }
3787
3788 #if ZIPPY
3789         /* Send move to chess program (BEFORE animating it). */
3790         if (appData.zippyPlay && !newGame && newMove && 
3791            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3792
3793             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3794                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3795                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3796                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3797                             move_str);
3798                     DisplayError(str, 0);
3799                 } else {
3800                     if (first.sendTime) {
3801                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3802                     }
3803                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3804                     if (firstMove && !bookHit) {
3805                         firstMove = FALSE;
3806                         if (first.useColors) {
3807                           SendToProgram(gameMode == IcsPlayingWhite ?
3808                                         "white\ngo\n" :
3809                                         "black\ngo\n", &first);
3810                         } else {
3811                           SendToProgram("go\n", &first);
3812                         }
3813                         first.maybeThinking = TRUE;
3814                     }
3815                 }
3816             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3817               if (moveList[moveNum - 1][0] == NULLCHAR) {
3818                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3819                 DisplayError(str, 0);
3820               } else {
3821                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3822                 SendMoveToProgram(moveNum - 1, &first);
3823               }
3824             }
3825         }
3826 #endif
3827     }
3828
3829     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3830         /* If move comes from a remote source, animate it.  If it
3831            isn't remote, it will have already been animated. */
3832         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3833             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3834         }
3835         if (!pausing && appData.highlightLastMove) {
3836             SetHighlights(fromX, fromY, toX, toY);
3837         }
3838     }
3839     
3840     /* Start the clocks */
3841     whiteFlag = blackFlag = FALSE;
3842     appData.clockMode = !(basetime == 0 && increment == 0);
3843     if (ticking == 0) {
3844       ics_clock_paused = TRUE;
3845       StopClocks();
3846     } else if (ticking == 1) {
3847       ics_clock_paused = FALSE;
3848     }
3849     if (gameMode == IcsIdle ||
3850         relation == RELATION_OBSERVING_STATIC ||
3851         relation == RELATION_EXAMINING ||
3852         ics_clock_paused)
3853       DisplayBothClocks();
3854     else
3855       StartClocks();
3856     
3857     /* Display opponents and material strengths */
3858     if (gameInfo.variant != VariantBughouse &&
3859         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3860         if (tinyLayout || smallLayout) {
3861             if(gameInfo.variant == VariantNormal)
3862                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3863                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3864                     basetime, increment);
3865             else
3866                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3867                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3868                     basetime, increment, (int) gameInfo.variant);
3869         } else {
3870             if(gameInfo.variant == VariantNormal)
3871                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3872                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3873                     basetime, increment);
3874             else
3875                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3876                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3877                     basetime, increment, VariantName(gameInfo.variant));
3878         }
3879         DisplayTitle(str);
3880   if (appData.debugMode) {
3881     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3882   }
3883     }
3884
3885    
3886     /* Display the board */
3887     if (!pausing && !appData.noGUI) {
3888       
3889       if (appData.premove)
3890           if (!gotPremove || 
3891              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3892              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3893               ClearPremoveHighlights();
3894
3895       DrawPosition(FALSE, boards[currentMove]);
3896       DisplayMove(moveNum - 1);
3897       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3898             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3899               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3900         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3901       }
3902     }
3903
3904     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3905 #if ZIPPY
3906     if(bookHit) { // [HGM] book: simulate book reply
3907         static char bookMove[MSG_SIZ]; // a bit generous?
3908
3909         programStats.nodes = programStats.depth = programStats.time = 
3910         programStats.score = programStats.got_only_move = 0;
3911         sprintf(programStats.movelist, "%s (xbook)", bookHit);
3912
3913         strcpy(bookMove, "move ");
3914         strcat(bookMove, bookHit);
3915         HandleMachineMove(bookMove, &first);
3916     }
3917 #endif
3918 }
3919
3920 void
3921 GetMoveListEvent()
3922 {
3923     char buf[MSG_SIZ];
3924     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3925         ics_getting_history = H_REQUESTED;
3926         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3927         SendToICS(buf);
3928     }
3929 }
3930
3931 void
3932 AnalysisPeriodicEvent(force)
3933      int force;
3934 {
3935     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3936          && !force) || !appData.periodicUpdates)
3937       return;
3938
3939     /* Send . command to Crafty to collect stats */
3940     SendToProgram(".\n", &first);
3941
3942     /* Don't send another until we get a response (this makes
3943        us stop sending to old Crafty's which don't understand
3944        the "." command (sending illegal cmds resets node count & time,
3945        which looks bad)) */
3946     programStats.ok_to_send = 0;
3947 }
3948
3949 void
3950 SendMoveToProgram(moveNum, cps)
3951      int moveNum;
3952      ChessProgramState *cps;
3953 {
3954     char buf[MSG_SIZ];
3955
3956     if (cps->useUsermove) {
3957       SendToProgram("usermove ", cps);
3958     }
3959     if (cps->useSAN) {
3960       char *space;
3961       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3962         int len = space - parseList[moveNum];
3963         memcpy(buf, parseList[moveNum], len);
3964         buf[len++] = '\n';
3965         buf[len] = NULLCHAR;
3966       } else {
3967         sprintf(buf, "%s\n", parseList[moveNum]);
3968       }
3969       SendToProgram(buf, cps);
3970     } else {
3971       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3972         AlphaRank(moveList[moveNum], 4);
3973         SendToProgram(moveList[moveNum], cps);
3974         AlphaRank(moveList[moveNum], 4); // and back
3975       } else
3976       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3977        * the engine. It would be nice to have a better way to identify castle 
3978        * moves here. */
3979       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3980                                                                          && cps->useOOCastle) {
3981         int fromX = moveList[moveNum][0] - AAA; 
3982         int fromY = moveList[moveNum][1] - ONE;
3983         int toX = moveList[moveNum][2] - AAA; 
3984         int toY = moveList[moveNum][3] - ONE;
3985         if((boards[moveNum][fromY][fromX] == WhiteKing 
3986             && boards[moveNum][toY][toX] == WhiteRook)
3987            || (boards[moveNum][fromY][fromX] == BlackKing 
3988                && boards[moveNum][toY][toX] == BlackRook)) {
3989           if(toX > fromX) SendToProgram("O-O\n", cps);
3990           else SendToProgram("O-O-O\n", cps);
3991         }
3992         else SendToProgram(moveList[moveNum], cps);
3993       }
3994       else SendToProgram(moveList[moveNum], cps);
3995       /* End of additions by Tord */
3996     }
3997
3998     /* [HGM] setting up the opening has brought engine in force mode! */
3999     /*       Send 'go' if we are in a mode where machine should play. */
4000     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4001         (gameMode == TwoMachinesPlay   ||
4002 #ifdef ZIPPY
4003          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4004 #endif
4005          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4006         SendToProgram("go\n", cps);
4007   if (appData.debugMode) {
4008     fprintf(debugFP, "(extra)\n");
4009   }
4010     }
4011     setboardSpoiledMachineBlack = 0;
4012 }
4013
4014 void
4015 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4016      ChessMove moveType;
4017      int fromX, fromY, toX, toY;
4018 {
4019     char user_move[MSG_SIZ];
4020
4021     switch (moveType) {
4022       default:
4023         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4024                 (int)moveType, fromX, fromY, toX, toY);
4025         DisplayError(user_move + strlen("say "), 0);
4026         break;
4027       case WhiteKingSideCastle:
4028       case BlackKingSideCastle:
4029       case WhiteQueenSideCastleWild:
4030       case BlackQueenSideCastleWild:
4031       /* PUSH Fabien */
4032       case WhiteHSideCastleFR:
4033       case BlackHSideCastleFR:
4034       /* POP Fabien */
4035         sprintf(user_move, "o-o\n");
4036         break;
4037       case WhiteQueenSideCastle:
4038       case BlackQueenSideCastle:
4039       case WhiteKingSideCastleWild:
4040       case BlackKingSideCastleWild:
4041       /* PUSH Fabien */
4042       case WhiteASideCastleFR:
4043       case BlackASideCastleFR:
4044       /* POP Fabien */
4045         sprintf(user_move, "o-o-o\n");
4046         break;
4047       case WhitePromotionQueen:
4048       case BlackPromotionQueen:
4049       case WhitePromotionRook:
4050       case BlackPromotionRook:
4051       case WhitePromotionBishop:
4052       case BlackPromotionBishop:
4053       case WhitePromotionKnight:
4054       case BlackPromotionKnight:
4055       case WhitePromotionKing:
4056       case BlackPromotionKing:
4057       case WhitePromotionChancellor:
4058       case BlackPromotionChancellor:
4059       case WhitePromotionArchbishop:
4060       case BlackPromotionArchbishop:
4061         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4062             sprintf(user_move, "%c%c%c%c=%c\n",
4063                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4064                 PieceToChar(WhiteFerz));
4065         else if(gameInfo.variant == VariantGreat)
4066             sprintf(user_move, "%c%c%c%c=%c\n",
4067                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4068                 PieceToChar(WhiteMan));
4069         else
4070             sprintf(user_move, "%c%c%c%c=%c\n",
4071                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4072                 PieceToChar(PromoPiece(moveType)));
4073         break;
4074       case WhiteDrop:
4075       case BlackDrop:
4076         sprintf(user_move, "%c@%c%c\n",
4077                 ToUpper(PieceToChar((ChessSquare) fromX)),
4078                 AAA + toX, ONE + toY);
4079         break;
4080       case NormalMove:
4081       case WhiteCapturesEnPassant:
4082       case BlackCapturesEnPassant:
4083       case IllegalMove:  /* could be a variant we don't quite understand */
4084         sprintf(user_move, "%c%c%c%c\n",
4085                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4086         break;
4087     }
4088     SendToICS(user_move);
4089 }
4090
4091 void
4092 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4093      int rf, ff, rt, ft;
4094      char promoChar;
4095      char move[7];
4096 {
4097     if (rf == DROP_RANK) {
4098         sprintf(move, "%c@%c%c\n",
4099                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4100     } else {
4101         if (promoChar == 'x' || promoChar == NULLCHAR) {
4102             sprintf(move, "%c%c%c%c\n",
4103                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4104         } else {
4105             sprintf(move, "%c%c%c%c%c\n",
4106                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4107         }
4108     }
4109 }
4110
4111 void
4112 ProcessICSInitScript(f)
4113      FILE *f;
4114 {
4115     char buf[MSG_SIZ];
4116
4117     while (fgets(buf, MSG_SIZ, f)) {
4118         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4119     }
4120
4121     fclose(f);
4122 }
4123
4124
4125 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4126 void
4127 AlphaRank(char *move, int n)
4128 {
4129 //    char *p = move, c; int x, y;
4130
4131     if (appData.debugMode) {
4132         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4133     }
4134
4135     if(move[1]=='*' && 
4136        move[2]>='0' && move[2]<='9' &&
4137        move[3]>='a' && move[3]<='x'    ) {
4138         move[1] = '@';
4139         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4140         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4141     } else
4142     if(move[0]>='0' && move[0]<='9' &&
4143        move[1]>='a' && move[1]<='x' &&
4144        move[2]>='0' && move[2]<='9' &&
4145        move[3]>='a' && move[3]<='x'    ) {
4146         /* input move, Shogi -> normal */
4147         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4148         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4149         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4150         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4151     } else
4152     if(move[1]=='@' &&
4153        move[3]>='0' && move[3]<='9' &&
4154        move[2]>='a' && move[2]<='x'    ) {
4155         move[1] = '*';
4156         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4157         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4158     } else
4159     if(
4160        move[0]>='a' && move[0]<='x' &&
4161        move[3]>='0' && move[3]<='9' &&
4162        move[2]>='a' && move[2]<='x'    ) {
4163          /* output move, normal -> Shogi */
4164         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4165         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4166         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4167         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4168         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4169     }
4170     if (appData.debugMode) {
4171         fprintf(debugFP, "   out = '%s'\n", move);
4172     }
4173 }
4174
4175 /* Parser for moves from gnuchess, ICS, or user typein box */
4176 Boolean
4177 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4178      char *move;
4179      int moveNum;
4180      ChessMove *moveType;
4181      int *fromX, *fromY, *toX, *toY;
4182      char *promoChar;
4183 {       
4184     if (appData.debugMode) {
4185         fprintf(debugFP, "move to parse: %s\n", move);
4186     }
4187     *moveType = yylexstr(moveNum, move);
4188
4189     switch (*moveType) {
4190       case WhitePromotionChancellor:
4191       case BlackPromotionChancellor:
4192       case WhitePromotionArchbishop:
4193       case BlackPromotionArchbishop:
4194       case WhitePromotionQueen:
4195       case BlackPromotionQueen:
4196       case WhitePromotionRook:
4197       case BlackPromotionRook:
4198       case WhitePromotionBishop:
4199       case BlackPromotionBishop:
4200       case WhitePromotionKnight:
4201       case BlackPromotionKnight:
4202       case WhitePromotionKing:
4203       case BlackPromotionKing:
4204       case NormalMove:
4205       case WhiteCapturesEnPassant:
4206       case BlackCapturesEnPassant:
4207       case WhiteKingSideCastle:
4208       case WhiteQueenSideCastle:
4209       case BlackKingSideCastle:
4210       case BlackQueenSideCastle:
4211       case WhiteKingSideCastleWild:
4212       case WhiteQueenSideCastleWild:
4213       case BlackKingSideCastleWild:
4214       case BlackQueenSideCastleWild:
4215       /* Code added by Tord: */
4216       case WhiteHSideCastleFR:
4217       case WhiteASideCastleFR:
4218       case BlackHSideCastleFR:
4219       case BlackASideCastleFR:
4220       /* End of code added by Tord */
4221       case IllegalMove:         /* bug or odd chess variant */
4222         *fromX = currentMoveString[0] - AAA;
4223         *fromY = currentMoveString[1] - ONE;
4224         *toX = currentMoveString[2] - AAA;
4225         *toY = currentMoveString[3] - ONE;
4226         *promoChar = currentMoveString[4];
4227         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4228             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4229     if (appData.debugMode) {
4230         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4231     }
4232             *fromX = *fromY = *toX = *toY = 0;
4233             return FALSE;
4234         }
4235         if (appData.testLegality) {
4236           return (*moveType != IllegalMove);
4237         } else {
4238           return !(fromX == fromY && toX == toY);
4239         }
4240
4241       case WhiteDrop:
4242       case BlackDrop:
4243         *fromX = *moveType == WhiteDrop ?
4244           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4245           (int) CharToPiece(ToLower(currentMoveString[0]));
4246         *fromY = DROP_RANK;
4247         *toX = currentMoveString[2] - AAA;
4248         *toY = currentMoveString[3] - ONE;
4249         *promoChar = NULLCHAR;
4250         return TRUE;
4251
4252       case AmbiguousMove:
4253       case ImpossibleMove:
4254       case (ChessMove) 0:       /* end of file */
4255       case ElapsedTime:
4256       case Comment:
4257       case PGNTag:
4258       case NAG:
4259       case WhiteWins:
4260       case BlackWins:
4261       case GameIsDrawn:
4262       default:
4263     if (appData.debugMode) {
4264         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4265     }
4266         /* bug? */
4267         *fromX = *fromY = *toX = *toY = 0;
4268         *promoChar = NULLCHAR;
4269         return FALSE;
4270     }
4271 }
4272
4273 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4274 // All positions will have equal probability, but the current method will not provide a unique
4275 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4276 #define DARK 1
4277 #define LITE 2
4278 #define ANY 3
4279
4280 int squaresLeft[4];
4281 int piecesLeft[(int)BlackPawn];
4282 int seed, nrOfShuffles;
4283
4284 void GetPositionNumber()
4285 {       // sets global variable seed
4286         int i;
4287
4288         seed = appData.defaultFrcPosition;
4289         if(seed < 0) { // randomize based on time for negative FRC position numbers
4290                 for(i=0; i<50; i++) seed += random();
4291                 seed = random() ^ random() >> 8 ^ random() << 8;
4292                 if(seed<0) seed = -seed;
4293         }
4294 }
4295
4296 int put(Board board, int pieceType, int rank, int n, int shade)
4297 // put the piece on the (n-1)-th empty squares of the given shade
4298 {
4299         int i;
4300
4301         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4302                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4303                         board[rank][i] = (ChessSquare) pieceType;
4304                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4305                         squaresLeft[ANY]--;
4306                         piecesLeft[pieceType]--; 
4307                         return i;
4308                 }
4309         }
4310         return -1;
4311 }
4312
4313
4314 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4315 // calculate where the next piece goes, (any empty square), and put it there
4316 {
4317         int i;
4318
4319         i = seed % squaresLeft[shade];
4320         nrOfShuffles *= squaresLeft[shade];
4321         seed /= squaresLeft[shade];
4322         put(board, pieceType, rank, i, shade);
4323 }
4324
4325 void AddTwoPieces(Board board, int pieceType, int rank)
4326 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4327 {
4328         int i, n=squaresLeft[ANY], j=n-1, k;
4329
4330         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4331         i = seed % k;  // pick one
4332         nrOfShuffles *= k;
4333         seed /= k;
4334         while(i >= j) i -= j--;
4335         j = n - 1 - j; i += j;
4336         put(board, pieceType, rank, j, ANY);
4337         put(board, pieceType, rank, i, ANY);
4338 }
4339
4340 void SetUpShuffle(Board board, int number)
4341 {
4342         int i, p, first=1;
4343
4344         GetPositionNumber(); nrOfShuffles = 1;
4345
4346         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4347         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4348         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4349
4350         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4351
4352         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4353             p = (int) board[0][i];
4354             if(p < (int) BlackPawn) piecesLeft[p] ++;
4355             board[0][i] = EmptySquare;
4356         }
4357
4358         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4359             // shuffles restricted to allow normal castling put KRR first
4360             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4361                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4362             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4363                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4364             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4365                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4366             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4367                 put(board, WhiteRook, 0, 0, ANY);
4368             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4369         }
4370
4371         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4372             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4373             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4374                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4375                 while(piecesLeft[p] >= 2) {
4376                     AddOnePiece(board, p, 0, LITE);
4377                     AddOnePiece(board, p, 0, DARK);
4378                 }
4379                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4380             }
4381
4382         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4383             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4384             // but we leave King and Rooks for last, to possibly obey FRC restriction
4385             if(p == (int)WhiteRook) continue;
4386             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4387             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4388         }
4389
4390         // now everything is placed, except perhaps King (Unicorn) and Rooks
4391
4392         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4393             // Last King gets castling rights
4394             while(piecesLeft[(int)WhiteUnicorn]) {
4395                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4396                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4397             }
4398
4399             while(piecesLeft[(int)WhiteKing]) {
4400                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4401                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4402             }
4403
4404
4405         } else {
4406             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4407             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4408         }
4409
4410         // Only Rooks can be left; simply place them all
4411         while(piecesLeft[(int)WhiteRook]) {
4412                 i = put(board, WhiteRook, 0, 0, ANY);
4413                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4414                         if(first) {
4415                                 first=0;
4416                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4417                         }
4418                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4419                 }
4420         }
4421         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4422             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4423         }
4424
4425         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4426 }
4427
4428 int SetCharTable( char *table, const char * map )
4429 /* [HGM] moved here from winboard.c because of its general usefulness */
4430 /*       Basically a safe strcpy that uses the last character as King */
4431 {
4432     int result = FALSE; int NrPieces;
4433
4434     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4435                     && NrPieces >= 12 && !(NrPieces&1)) {
4436         int i; /* [HGM] Accept even length from 12 to 34 */
4437
4438         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4439         for( i=0; i<NrPieces/2-1; i++ ) {
4440             table[i] = map[i];
4441             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4442         }
4443         table[(int) WhiteKing]  = map[NrPieces/2-1];
4444         table[(int) BlackKing]  = map[NrPieces-1];
4445
4446         result = TRUE;
4447     }
4448
4449     return result;
4450 }
4451
4452 void Prelude(Board board)
4453 {       // [HGM] superchess: random selection of exo-pieces
4454         int i, j, k; ChessSquare p; 
4455         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4456
4457         GetPositionNumber(); // use FRC position number
4458
4459         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4460             SetCharTable(pieceToChar, appData.pieceToCharTable);
4461             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4462                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4463         }
4464
4465         j = seed%4;                 seed /= 4; 
4466         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4467         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4468         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4469         j = seed%3 + (seed%3 >= j); seed /= 3; 
4470         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4471         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4472         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4473         j = seed%3;                 seed /= 3; 
4474         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4475         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4476         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4477         j = seed%2 + (seed%2 >= j); seed /= 2; 
4478         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4479         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4480         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4481         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4482         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4483         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4484         put(board, exoPieces[0],    0, 0, ANY);
4485         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4486 }
4487
4488 void
4489 InitPosition(redraw)
4490      int redraw;
4491 {
4492     ChessSquare (* pieces)[BOARD_SIZE];
4493     int i, j, pawnRow, overrule,
4494     oldx = gameInfo.boardWidth,
4495     oldy = gameInfo.boardHeight,
4496     oldh = gameInfo.holdingsWidth,
4497     oldv = gameInfo.variant;
4498
4499     currentMove = forwardMostMove = backwardMostMove = 0;
4500     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4501
4502     /* [AS] Initialize pv info list [HGM] and game status */
4503     {
4504         for( i=0; i<MAX_MOVES; i++ ) {
4505             pvInfoList[i].depth = 0;
4506             epStatus[i]=EP_NONE;
4507             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4508         }
4509
4510         initialRulePlies = 0; /* 50-move counter start */
4511
4512         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4513         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4514     }
4515
4516     
4517     /* [HGM] logic here is completely changed. In stead of full positions */
4518     /* the initialized data only consist of the two backranks. The switch */
4519     /* selects which one we will use, which is than copied to the Board   */
4520     /* initialPosition, which for the rest is initialized by Pawns and    */
4521     /* empty squares. This initial position is then copied to boards[0],  */
4522     /* possibly after shuffling, so that it remains available.            */
4523
4524     gameInfo.holdingsWidth = 0; /* default board sizes */
4525     gameInfo.boardWidth    = 8;
4526     gameInfo.boardHeight   = 8;
4527     gameInfo.holdingsSize  = 0;
4528     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4529     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4530     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4531
4532     switch (gameInfo.variant) {
4533     case VariantFischeRandom:
4534       shuffleOpenings = TRUE;
4535     default:
4536       pieces = FIDEArray;
4537       break;
4538     case VariantShatranj:
4539       pieces = ShatranjArray;
4540       nrCastlingRights = 0;
4541       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4542       break;
4543     case VariantTwoKings:
4544       pieces = twoKingsArray;
4545       break;
4546     case VariantCapaRandom:
4547       shuffleOpenings = TRUE;
4548     case VariantCapablanca:
4549       pieces = CapablancaArray;
4550       gameInfo.boardWidth = 10;
4551       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4552       break;
4553     case VariantGothic:
4554       pieces = GothicArray;
4555       gameInfo.boardWidth = 10;
4556       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4557       break;
4558     case VariantJanus:
4559       pieces = JanusArray;
4560       gameInfo.boardWidth = 10;
4561       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4562       nrCastlingRights = 6;
4563         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4564         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4565         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4566         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4567         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4568         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4569       break;
4570     case VariantFalcon:
4571       pieces = FalconArray;
4572       gameInfo.boardWidth = 10;
4573       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4574       break;
4575     case VariantXiangqi:
4576       pieces = XiangqiArray;
4577       gameInfo.boardWidth  = 9;
4578       gameInfo.boardHeight = 10;
4579       nrCastlingRights = 0;
4580       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4581       break;
4582     case VariantShogi:
4583       pieces = ShogiArray;
4584       gameInfo.boardWidth  = 9;
4585       gameInfo.boardHeight = 9;
4586       gameInfo.holdingsSize = 7;
4587       nrCastlingRights = 0;
4588       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4589       break;
4590     case VariantCourier:
4591       pieces = CourierArray;
4592       gameInfo.boardWidth  = 12;
4593       nrCastlingRights = 0;
4594       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4595       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4596       break;
4597     case VariantKnightmate:
4598       pieces = KnightmateArray;
4599       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4600       break;
4601     case VariantFairy:
4602       pieces = fairyArray;
4603       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4604       break;
4605     case VariantGreat:
4606       pieces = GreatArray;
4607       gameInfo.boardWidth = 10;
4608       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4609       gameInfo.holdingsSize = 8;
4610       break;
4611     case VariantSuper:
4612       pieces = FIDEArray;
4613       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4614       gameInfo.holdingsSize = 8;
4615       startedFromSetupPosition = TRUE;
4616       break;
4617     case VariantCrazyhouse:
4618     case VariantBughouse:
4619       pieces = FIDEArray;
4620       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4621       gameInfo.holdingsSize = 5;
4622       break;
4623     case VariantWildCastle:
4624       pieces = FIDEArray;
4625       /* !!?shuffle with kings guaranteed to be on d or e file */
4626       shuffleOpenings = 1;
4627       break;
4628     case VariantNoCastle:
4629       pieces = FIDEArray;
4630       nrCastlingRights = 0;
4631       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4632       /* !!?unconstrained back-rank shuffle */
4633       shuffleOpenings = 1;
4634       break;
4635     }
4636
4637     overrule = 0;
4638     if(appData.NrFiles >= 0) {
4639         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4640         gameInfo.boardWidth = appData.NrFiles;
4641     }
4642     if(appData.NrRanks >= 0) {
4643         gameInfo.boardHeight = appData.NrRanks;
4644     }
4645     if(appData.holdingsSize >= 0) {
4646         i = appData.holdingsSize;
4647         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4648         gameInfo.holdingsSize = i;
4649     }
4650     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4651     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4652         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4653
4654     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4655     if(pawnRow < 1) pawnRow = 1;
4656
4657     /* User pieceToChar list overrules defaults */
4658     if(appData.pieceToCharTable != NULL)
4659         SetCharTable(pieceToChar, appData.pieceToCharTable);
4660
4661     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4662
4663         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4664             s = (ChessSquare) 0; /* account holding counts in guard band */
4665         for( i=0; i<BOARD_HEIGHT; i++ )
4666             initialPosition[i][j] = s;
4667
4668         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4669         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4670         initialPosition[pawnRow][j] = WhitePawn;
4671         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4672         if(gameInfo.variant == VariantXiangqi) {
4673             if(j&1) {
4674                 initialPosition[pawnRow][j] = 
4675                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4676                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4677                    initialPosition[2][j] = WhiteCannon;
4678                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4679                 }
4680             }
4681         }
4682         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4683     }
4684     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4685
4686             j=BOARD_LEFT+1;
4687             initialPosition[1][j] = WhiteBishop;
4688             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4689             j=BOARD_RGHT-2;
4690             initialPosition[1][j] = WhiteRook;
4691             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4692     }
4693
4694     if( nrCastlingRights == -1) {
4695         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4696         /*       This sets default castling rights from none to normal corners   */
4697         /* Variants with other castling rights must set them themselves above    */
4698         nrCastlingRights = 6;
4699        
4700         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4701         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4702         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4703         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4704         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4705         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4706      }
4707
4708      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4709      if(gameInfo.variant == VariantGreat) { // promotion commoners
4710         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4711         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4712         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4713         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4714      }
4715 #if 0
4716     if(gameInfo.variant == VariantFischeRandom) {
4717       if( appData.defaultFrcPosition < 0 ) {
4718         ShuffleFRC( initialPosition );
4719       }
4720       else {
4721         SetupFRC( initialPosition, appData.defaultFrcPosition );
4722       }
4723       startedFromSetupPosition = TRUE;
4724     } else 
4725 #else
4726   if (appData.debugMode) {
4727     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4728   }
4729     if(shuffleOpenings) {
4730         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4731         startedFromSetupPosition = TRUE;
4732     }
4733 #endif
4734     if(startedFromPositionFile) {
4735       /* [HGM] loadPos: use PositionFile for every new game */
4736       CopyBoard(initialPosition, filePosition);
4737       for(i=0; i<nrCastlingRights; i++)
4738           castlingRights[0][i] = initialRights[i] = fileRights[i];
4739       startedFromSetupPosition = TRUE;
4740     }
4741
4742     CopyBoard(boards[0], initialPosition);
4743
4744     if(oldx != gameInfo.boardWidth ||
4745        oldy != gameInfo.boardHeight ||
4746        oldh != gameInfo.holdingsWidth
4747 #ifdef GOTHIC
4748        || oldv == VariantGothic ||        // For licensing popups
4749        gameInfo.variant == VariantGothic
4750 #endif
4751 #ifdef FALCON
4752        || oldv == VariantFalcon ||
4753        gameInfo.variant == VariantFalcon
4754 #endif
4755                                          )
4756             InitDrawingSizes(-2 ,0);
4757
4758     if (redraw)
4759       DrawPosition(TRUE, boards[currentMove]);
4760 }
4761
4762 void
4763 SendBoard(cps, moveNum)
4764      ChessProgramState *cps;
4765      int moveNum;
4766 {
4767     char message[MSG_SIZ];
4768     
4769     if (cps->useSetboard) {
4770       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4771       sprintf(message, "setboard %s\n", fen);
4772       SendToProgram(message, cps);
4773       free(fen);
4774
4775     } else {
4776       ChessSquare *bp;
4777       int i, j;
4778       /* Kludge to set black to move, avoiding the troublesome and now
4779        * deprecated "black" command.
4780        */
4781       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4782
4783       SendToProgram("edit\n", cps);
4784       SendToProgram("#\n", cps);
4785       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4786         bp = &boards[moveNum][i][BOARD_LEFT];
4787         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4788           if ((int) *bp < (int) BlackPawn) {
4789             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4790                     AAA + j, ONE + i);
4791             if(message[0] == '+' || message[0] == '~') {
4792                 sprintf(message, "%c%c%c+\n",
4793                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4794                         AAA + j, ONE + i);
4795             }
4796             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4797                 message[1] = BOARD_RGHT   - 1 - j + '1';
4798                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4799             }
4800             SendToProgram(message, cps);
4801           }
4802         }
4803       }
4804     
4805       SendToProgram("c\n", cps);
4806       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4807         bp = &boards[moveNum][i][BOARD_LEFT];
4808         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4809           if (((int) *bp != (int) EmptySquare)
4810               && ((int) *bp >= (int) BlackPawn)) {
4811             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4812                     AAA + j, ONE + i);
4813             if(message[0] == '+' || message[0] == '~') {
4814                 sprintf(message, "%c%c%c+\n",
4815                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4816                         AAA + j, ONE + i);
4817             }
4818             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4819                 message[1] = BOARD_RGHT   - 1 - j + '1';
4820                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4821             }
4822             SendToProgram(message, cps);
4823           }
4824         }
4825       }
4826     
4827       SendToProgram(".\n", cps);
4828     }
4829     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4830 }
4831
4832 int
4833 IsPromotion(fromX, fromY, toX, toY)
4834      int fromX, fromY, toX, toY;
4835 {
4836     /* [HGM] add Shogi promotions */
4837     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4838     ChessSquare piece;
4839
4840     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4841       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4842    /* [HGM] Note to self: line above also weeds out drops */
4843     piece = boards[currentMove][fromY][fromX];
4844     if(gameInfo.variant == VariantShogi) {
4845         promotionZoneSize = 3;
4846         highestPromotingPiece = (int)WhiteKing;
4847         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4848            and if in normal chess we then allow promotion to King, why not
4849            allow promotion of other piece in Shogi?                         */
4850     }
4851     if((int)piece >= BlackPawn) {
4852         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4853              return FALSE;
4854         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4855     } else {
4856         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4857            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4858     }
4859     return ( (int)piece <= highestPromotingPiece );
4860 }
4861
4862 int
4863 InPalace(row, column)
4864      int row, column;
4865 {   /* [HGM] for Xiangqi */
4866     if( (row < 3 || row > BOARD_HEIGHT-4) &&
4867          column < (BOARD_WIDTH + 4)/2 &&
4868          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4869     return FALSE;
4870 }
4871
4872 int
4873 PieceForSquare (x, y)
4874      int x;
4875      int y;
4876 {
4877   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4878      return -1;
4879   else
4880      return boards[currentMove][y][x];
4881 }
4882
4883 int
4884 OKToStartUserMove(x, y)
4885      int x, y;
4886 {
4887     ChessSquare from_piece;
4888     int white_piece;
4889
4890     if (matchMode) return FALSE;
4891     if (gameMode == EditPosition) return TRUE;
4892
4893     if (x >= 0 && y >= 0)
4894       from_piece = boards[currentMove][y][x];
4895     else
4896       from_piece = EmptySquare;
4897
4898     if (from_piece == EmptySquare) return FALSE;
4899
4900     white_piece = (int)from_piece >= (int)WhitePawn &&
4901       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4902
4903     switch (gameMode) {
4904       case PlayFromGameFile:
4905       case AnalyzeFile:
4906       case TwoMachinesPlay:
4907       case EndOfGame:
4908         return FALSE;
4909
4910       case IcsObserving:
4911       case IcsIdle:
4912         return FALSE;
4913
4914       case MachinePlaysWhite:
4915       case IcsPlayingBlack:
4916         if (appData.zippyPlay) return FALSE;
4917         if (white_piece) {
4918             DisplayMoveError(_("You are playing Black"));
4919             return FALSE;
4920         }
4921         break;
4922
4923       case MachinePlaysBlack:
4924       case IcsPlayingWhite:
4925         if (appData.zippyPlay) return FALSE;
4926         if (!white_piece) {
4927             DisplayMoveError(_("You are playing White"));
4928             return FALSE;
4929         }
4930         break;
4931
4932       case EditGame:
4933         if (!white_piece && WhiteOnMove(currentMove)) {
4934             DisplayMoveError(_("It is White's turn"));
4935             return FALSE;
4936         }           
4937         if (white_piece && !WhiteOnMove(currentMove)) {
4938             DisplayMoveError(_("It is Black's turn"));
4939             return FALSE;
4940         }           
4941         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4942             /* Editing correspondence game history */
4943             /* Could disallow this or prompt for confirmation */
4944             cmailOldMove = -1;
4945         }
4946         if (currentMove < forwardMostMove) {
4947             /* Discarding moves */
4948             /* Could prompt for confirmation here,
4949                but I don't think that's such a good idea */
4950             forwardMostMove = currentMove;
4951         }
4952         break;
4953
4954       case BeginningOfGame:
4955         if (appData.icsActive) return FALSE;
4956         if (!appData.noChessProgram) {
4957             if (!white_piece) {
4958                 DisplayMoveError(_("You are playing White"));
4959                 return FALSE;
4960             }
4961         }
4962         break;
4963         
4964       case Training:
4965         if (!white_piece && WhiteOnMove(currentMove)) {
4966             DisplayMoveError(_("It is White's turn"));
4967             return FALSE;
4968         }           
4969         if (white_piece && !WhiteOnMove(currentMove)) {
4970             DisplayMoveError(_("It is Black's turn"));
4971             return FALSE;
4972         }           
4973         break;
4974
4975       default:
4976       case IcsExamining:
4977         break;
4978     }
4979     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
4980         && gameMode != AnalyzeFile && gameMode != Training) {
4981         DisplayMoveError(_("Displayed position is not current"));
4982         return FALSE;
4983     }
4984     return TRUE;
4985 }
4986
4987 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
4988 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
4989 int lastLoadGameUseList = FALSE;
4990 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
4991 ChessMove lastLoadGameStart = (ChessMove) 0;
4992
4993
4994 ChessMove
4995 UserMoveTest(fromX, fromY, toX, toY, promoChar)
4996      int fromX, fromY, toX, toY;
4997      int promoChar;
4998 {
4999     ChessMove moveType;
5000     ChessSquare pdown, pup;
5001
5002     if (fromX < 0 || fromY < 0) return ImpossibleMove;
5003     if ((fromX == toX) && (fromY == toY)) {
5004         return ImpossibleMove;
5005     }
5006
5007     /* [HGM] suppress all moves into holdings area and guard band */
5008     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5009             return ImpossibleMove;
5010
5011     /* [HGM] <sameColor> moved to here from winboard.c */
5012     /* note: this code seems to exist for filtering out some obviously illegal premoves */
5013     pdown = boards[currentMove][fromY][fromX];
5014     pup = boards[currentMove][toY][toX];
5015     if (    gameMode != EditPosition &&
5016             (WhitePawn <= pdown && pdown < BlackPawn &&
5017              WhitePawn <= pup && pup < BlackPawn  ||
5018              BlackPawn <= pdown && pdown < EmptySquare &&
5019              BlackPawn <= pup && pup < EmptySquare 
5020             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5021                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5022                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) 
5023         )           )
5024          return ImpossibleMove;
5025
5026     /* Check if the user is playing in turn.  This is complicated because we
5027        let the user "pick up" a piece before it is his turn.  So the piece he
5028        tried to pick up may have been captured by the time he puts it down!
5029        Therefore we use the color the user is supposed to be playing in this
5030        test, not the color of the piece that is currently on the starting
5031        square---except in EditGame mode, where the user is playing both
5032        sides; fortunately there the capture race can't happen.  (It can
5033        now happen in IcsExamining mode, but that's just too bad.  The user
5034        will get a somewhat confusing message in that case.)
5035        */
5036
5037     switch (gameMode) {
5038       case PlayFromGameFile:
5039       case AnalyzeFile:
5040       case TwoMachinesPlay:
5041       case EndOfGame:
5042       case IcsObserving:
5043       case IcsIdle:
5044         /* We switched into a game mode where moves are not accepted,
5045            perhaps while the mouse button was down. */
5046         return ImpossibleMove;
5047
5048       case MachinePlaysWhite:
5049         /* User is moving for Black */
5050         if (WhiteOnMove(currentMove)) {
5051             DisplayMoveError(_("It is White's turn"));
5052             return ImpossibleMove;
5053         }
5054         break;
5055
5056       case MachinePlaysBlack:
5057         /* User is moving for White */
5058         if (!WhiteOnMove(currentMove)) {
5059             DisplayMoveError(_("It is Black's turn"));
5060             return ImpossibleMove;
5061         }
5062         break;
5063
5064       case EditGame:
5065       case IcsExamining:
5066       case BeginningOfGame:
5067       case AnalyzeMode:
5068       case Training:
5069         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5070             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5071             /* User is moving for Black */
5072             if (WhiteOnMove(currentMove)) {
5073                 DisplayMoveError(_("It is White's turn"));
5074                 return ImpossibleMove;
5075             }
5076         } else {
5077             /* User is moving for White */
5078             if (!WhiteOnMove(currentMove)) {
5079                 DisplayMoveError(_("It is Black's turn"));
5080                 return ImpossibleMove;
5081             }
5082         }
5083         break;
5084
5085       case IcsPlayingBlack:
5086         /* User is moving for Black */
5087         if (WhiteOnMove(currentMove)) {
5088             if (!appData.premove) {
5089                 DisplayMoveError(_("It is White's turn"));
5090             } else if (toX >= 0 && toY >= 0) {
5091                 premoveToX = toX;
5092                 premoveToY = toY;
5093                 premoveFromX = fromX;
5094                 premoveFromY = fromY;
5095                 premovePromoChar = promoChar;
5096                 gotPremove = 1;
5097                 if (appData.debugMode) 
5098                     fprintf(debugFP, "Got premove: fromX %d,"
5099                             "fromY %d, toX %d, toY %d\n",
5100                             fromX, fromY, toX, toY);
5101             }
5102             return ImpossibleMove;
5103         }
5104         break;
5105
5106       case IcsPlayingWhite:
5107         /* User is moving for White */
5108         if (!WhiteOnMove(currentMove)) {
5109             if (!appData.premove) {
5110                 DisplayMoveError(_("It is Black's turn"));
5111             } else if (toX >= 0 && toY >= 0) {
5112                 premoveToX = toX;
5113                 premoveToY = toY;
5114                 premoveFromX = fromX;
5115                 premoveFromY = fromY;
5116                 premovePromoChar = promoChar;
5117                 gotPremove = 1;
5118                 if (appData.debugMode) 
5119                     fprintf(debugFP, "Got premove: fromX %d,"
5120                             "fromY %d, toX %d, toY %d\n",
5121                             fromX, fromY, toX, toY);
5122             }
5123             return ImpossibleMove;
5124         }
5125         break;
5126
5127       default:
5128         break;
5129
5130       case EditPosition:
5131         /* EditPosition, empty square, or different color piece;
5132            click-click move is possible */
5133         if (toX == -2 || toY == -2) {
5134             boards[0][fromY][fromX] = EmptySquare;
5135             return AmbiguousMove;
5136         } else if (toX >= 0 && toY >= 0) {
5137             boards[0][toY][toX] = boards[0][fromY][fromX];
5138             boards[0][fromY][fromX] = EmptySquare;
5139             return AmbiguousMove;
5140         }
5141         return ImpossibleMove;
5142     }
5143
5144     /* [HGM] If move started in holdings, it means a drop */
5145     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5146          if( pup != EmptySquare ) return ImpossibleMove;
5147          if(appData.testLegality) {
5148              /* it would be more logical if LegalityTest() also figured out
5149               * which drops are legal. For now we forbid pawns on back rank.
5150               * Shogi is on its own here...
5151               */
5152              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5153                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5154                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5155          }
5156          return WhiteDrop; /* Not needed to specify white or black yet */
5157     }
5158
5159     userOfferedDraw = FALSE;
5160         
5161     /* [HGM] always test for legality, to get promotion info */
5162     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5163                           epStatus[currentMove], castlingRights[currentMove],
5164                                          fromY, fromX, toY, toX, promoChar);
5165
5166     /* [HGM] but possibly ignore an IllegalMove result */
5167     if (appData.testLegality) {
5168         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5169             DisplayMoveError(_("Illegal move"));
5170             return ImpossibleMove;
5171         }
5172     }
5173 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5174     return moveType;
5175     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5176        function is made into one that returns an OK move type if FinishMove
5177        should be called. This to give the calling driver routine the
5178        opportunity to finish the userMove input with a promotion popup,
5179        without bothering the user with this for invalid or illegal moves */
5180
5181 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5182 }
5183
5184 /* Common tail of UserMoveEvent and DropMenuEvent */
5185 int
5186 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5187      ChessMove moveType;
5188      int fromX, fromY, toX, toY;
5189      /*char*/int promoChar;
5190 {
5191     char *bookHit = 0;
5192 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5193     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5194         // [HGM] superchess: suppress promotions to non-available piece
5195         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5196         if(WhiteOnMove(currentMove)) {
5197             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5198         } else {
5199             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5200         }
5201     }
5202
5203     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5204        move type in caller when we know the move is a legal promotion */
5205     if(moveType == NormalMove && promoChar)
5206         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5207 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5208     /* [HGM] convert drag-and-drop piece drops to standard form */
5209     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5210          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5211            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5212                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5213 //         fromX = boards[currentMove][fromY][fromX];
5214            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5215            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5216            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5217            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5218          fromY = DROP_RANK;
5219     }
5220
5221     /* [HGM] <popupFix> The following if has been moved here from
5222        UserMoveEvent(). Because it seemed to belon here (why not allow
5223        piece drops in training games?), and because it can only be
5224        performed after it is known to what we promote. */
5225     if (gameMode == Training) {
5226       /* compare the move played on the board to the next move in the
5227        * game. If they match, display the move and the opponent's response. 
5228        * If they don't match, display an error message.
5229        */
5230       int saveAnimate;
5231       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5232       CopyBoard(testBoard, boards[currentMove]);
5233       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5234
5235       if (CompareBoards(testBoard, boards[currentMove+1])) {
5236         ForwardInner(currentMove+1);
5237
5238         /* Autoplay the opponent's response.
5239          * if appData.animate was TRUE when Training mode was entered,
5240          * the response will be animated.
5241          */
5242         saveAnimate = appData.animate;
5243         appData.animate = animateTraining;
5244         ForwardInner(currentMove+1);
5245         appData.animate = saveAnimate;
5246
5247         /* check for the end of the game */
5248         if (currentMove >= forwardMostMove) {
5249           gameMode = PlayFromGameFile;
5250           ModeHighlight();
5251           SetTrainingModeOff();
5252           DisplayInformation(_("End of game"));
5253         }
5254       } else {
5255         DisplayError(_("Incorrect move"), 0);
5256       }
5257       return 1;
5258     }
5259
5260   /* Ok, now we know that the move is good, so we can kill
5261      the previous line in Analysis Mode */
5262   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5263     forwardMostMove = currentMove;
5264   }
5265
5266   /* If we need the chess program but it's dead, restart it */
5267   ResurrectChessProgram();
5268
5269   /* A user move restarts a paused game*/
5270   if (pausing)
5271     PauseEvent();
5272
5273   thinkOutput[0] = NULLCHAR;
5274
5275   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5276
5277   if (gameMode == BeginningOfGame) {
5278     if (appData.noChessProgram) {
5279       gameMode = EditGame;
5280       SetGameInfo();
5281     } else {
5282       char buf[MSG_SIZ];
5283       gameMode = MachinePlaysBlack;
5284       StartClocks();
5285       SetGameInfo();
5286       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5287       DisplayTitle(buf);
5288       if (first.sendName) {
5289         sprintf(buf, "name %s\n", gameInfo.white);
5290         SendToProgram(buf, &first);
5291       }
5292       StartClocks();
5293     }
5294     ModeHighlight();
5295   }
5296 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5297   /* Relay move to ICS or chess engine */
5298   if (appData.icsActive) {
5299     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5300         gameMode == IcsExamining) {
5301       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5302       ics_user_moved = 1;
5303     }
5304   } else {
5305     if (first.sendTime && (gameMode == BeginningOfGame ||
5306                            gameMode == MachinePlaysWhite ||
5307                            gameMode == MachinePlaysBlack)) {
5308       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5309     }
5310     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5311          // [HGM] book: if program might be playing, let it use book
5312         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5313         first.maybeThinking = TRUE;
5314     } else SendMoveToProgram(forwardMostMove-1, &first);
5315     if (currentMove == cmailOldMove + 1) {
5316       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5317     }
5318   }
5319
5320   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5321
5322   switch (gameMode) {
5323   case EditGame:
5324     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5325                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5326     case MT_NONE:
5327     case MT_CHECK:
5328       break;
5329     case MT_CHECKMATE:
5330     case MT_STAINMATE:
5331       if (WhiteOnMove(currentMove)) {
5332         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5333       } else {
5334         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5335       }
5336       break;
5337     case MT_STALEMATE:
5338       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5339       break;
5340     }
5341     break;
5342     
5343   case MachinePlaysBlack:
5344   case MachinePlaysWhite:
5345     /* disable certain menu options while machine is thinking */
5346     SetMachineThinkingEnables();
5347     break;
5348
5349   default:
5350     break;
5351   }
5352
5353   if(bookHit) { // [HGM] book: simulate book reply
5354         static char bookMove[MSG_SIZ]; // a bit generous?
5355
5356         programStats.nodes = programStats.depth = programStats.time = 
5357         programStats.score = programStats.got_only_move = 0;
5358         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5359
5360         strcpy(bookMove, "move ");
5361         strcat(bookMove, bookHit);
5362         HandleMachineMove(bookMove, &first);
5363   }
5364   return 1;
5365 }
5366
5367 void
5368 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5369      int fromX, fromY, toX, toY;
5370      int promoChar;
5371 {
5372     /* [HGM] This routine was added to allow calling of its two logical
5373        parts from other modules in the old way. Before, UserMoveEvent()
5374        automatically called FinishMove() if the move was OK, and returned
5375        otherwise. I separated the two, in order to make it possible to
5376        slip a promotion popup in between. But that it always needs two
5377        calls, to the first part, (now called UserMoveTest() ), and to
5378        FinishMove if the first part succeeded. Calls that do not need
5379        to do anything in between, can call this routine the old way. 
5380     */
5381     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
5382 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5383     if(moveType != ImpossibleMove)
5384         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5385 }
5386
5387 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5388 {
5389 //    char * hint = lastHint;
5390     FrontEndProgramStats stats;
5391
5392     stats.which = cps == &first ? 0 : 1;
5393     stats.depth = cpstats->depth;
5394     stats.nodes = cpstats->nodes;
5395     stats.score = cpstats->score;
5396     stats.time = cpstats->time;
5397     stats.pv = cpstats->movelist;
5398     stats.hint = lastHint;
5399     stats.an_move_index = 0;
5400     stats.an_move_count = 0;
5401
5402     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5403         stats.hint = cpstats->move_name;
5404         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5405         stats.an_move_count = cpstats->nr_moves;
5406     }
5407
5408     SetProgramStats( &stats );
5409 }
5410
5411 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5412 {   // [HGM] book: this routine intercepts moves to simulate book replies
5413     char *bookHit = NULL;
5414
5415     //first determine if the incoming move brings opponent into his book
5416     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5417         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5418     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5419     if(bookHit != NULL && !cps->bookSuspend) {
5420         // make sure opponent is not going to reply after receiving move to book position
5421         SendToProgram("force\n", cps);
5422         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5423     }
5424     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5425     // now arrange restart after book miss
5426     if(bookHit) {
5427         // after a book hit we never send 'go', and the code after the call to this routine
5428         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5429         char buf[MSG_SIZ];
5430         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5431         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5432         SendToProgram(buf, cps);
5433         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5434     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5435         SendToProgram("go\n", cps);
5436         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5437     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5438         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5439             SendToProgram("go\n", cps); 
5440         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5441     }
5442     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5443 }
5444
5445 char *savedMessage;
5446 ChessProgramState *savedState;
5447 void DeferredBookMove(void)
5448 {
5449         if(savedState->lastPing != savedState->lastPong)
5450                     ScheduleDelayedEvent(DeferredBookMove, 10);
5451         else
5452         HandleMachineMove(savedMessage, savedState);
5453 }
5454
5455 void
5456 HandleMachineMove(message, cps)
5457      char *message;
5458      ChessProgramState *cps;
5459 {
5460     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5461     char realname[MSG_SIZ];
5462     int fromX, fromY, toX, toY;
5463     ChessMove moveType;
5464     char promoChar;
5465     char *p;
5466     int machineWhite;
5467     char *bookHit;
5468
5469 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5470     /*
5471      * Kludge to ignore BEL characters
5472      */
5473     while (*message == '\007') message++;
5474
5475     /*
5476      * [HGM] engine debug message: ignore lines starting with '#' character
5477      */
5478     if(cps->debug && *message == '#') return;
5479
5480     /*
5481      * Look for book output
5482      */
5483     if (cps == &first && bookRequested) {
5484         if (message[0] == '\t' || message[0] == ' ') {
5485             /* Part of the book output is here; append it */
5486             strcat(bookOutput, message);
5487             strcat(bookOutput, "  \n");
5488             return;
5489         } else if (bookOutput[0] != NULLCHAR) {
5490             /* All of book output has arrived; display it */
5491             char *p = bookOutput;
5492             while (*p != NULLCHAR) {
5493                 if (*p == '\t') *p = ' ';
5494                 p++;
5495             }
5496             DisplayInformation(bookOutput);
5497             bookRequested = FALSE;
5498             /* Fall through to parse the current output */
5499         }
5500     }
5501
5502     /*
5503      * Look for machine move.
5504      */
5505     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5506         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5507     {
5508         /* This method is only useful on engines that support ping */
5509         if (cps->lastPing != cps->lastPong) {
5510           if (gameMode == BeginningOfGame) {
5511             /* Extra move from before last new; ignore */
5512             if (appData.debugMode) {
5513                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5514             }
5515           } else {
5516             if (appData.debugMode) {
5517                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5518                         cps->which, gameMode);
5519             }
5520
5521             SendToProgram("undo\n", cps);
5522           }
5523           return;
5524         }
5525
5526         switch (gameMode) {
5527           case BeginningOfGame:
5528             /* Extra move from before last reset; ignore */
5529             if (appData.debugMode) {
5530                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5531             }
5532             return;
5533
5534           case EndOfGame:
5535           case IcsIdle:
5536           default:
5537             /* Extra move after we tried to stop.  The mode test is
5538                not a reliable way of detecting this problem, but it's
5539                the best we can do on engines that don't support ping.
5540             */
5541             if (appData.debugMode) {
5542                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5543                         cps->which, gameMode);
5544             }
5545             SendToProgram("undo\n", cps);
5546             return;
5547
5548           case MachinePlaysWhite:
5549           case IcsPlayingWhite:
5550             machineWhite = TRUE;
5551             break;
5552
5553           case MachinePlaysBlack:
5554           case IcsPlayingBlack:
5555             machineWhite = FALSE;
5556             break;
5557
5558           case TwoMachinesPlay:
5559             machineWhite = (cps->twoMachinesColor[0] == 'w');
5560             break;
5561         }
5562         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5563             if (appData.debugMode) {
5564                 fprintf(debugFP,
5565                         "Ignoring move out of turn by %s, gameMode %d"
5566                         ", forwardMost %d\n",
5567                         cps->which, gameMode, forwardMostMove);
5568             }
5569             return;
5570         }
5571
5572     if (appData.debugMode) { int f = forwardMostMove;
5573         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5574                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5575     }
5576         if(cps->alphaRank) AlphaRank(machineMove, 4);
5577         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5578                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5579             /* Machine move could not be parsed; ignore it. */
5580             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5581                     machineMove, cps->which);
5582             DisplayError(buf1, 0);
5583             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5584                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5585             if (gameMode == TwoMachinesPlay) {
5586               GameEnds(machineWhite ? BlackWins : WhiteWins,
5587                        buf1, GE_XBOARD);
5588             }
5589             return;
5590         }
5591
5592         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5593         /* So we have to redo legality test with true e.p. status here,  */
5594         /* to make sure an illegal e.p. capture does not slip through,   */
5595         /* to cause a forfeit on a justified illegal-move complaint      */
5596         /* of the opponent.                                              */
5597         if( gameMode==TwoMachinesPlay && appData.testLegality
5598             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5599                                                               ) {
5600            ChessMove moveType;
5601            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5602                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5603                              fromY, fromX, toY, toX, promoChar);
5604             if (appData.debugMode) {
5605                 int i;
5606                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5607                     castlingRights[forwardMostMove][i], castlingRank[i]);
5608                 fprintf(debugFP, "castling rights\n");
5609             }
5610             if(moveType == IllegalMove) {
5611                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5612                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5613                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5614                            buf1, GE_XBOARD);
5615                 return;
5616            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5617            /* [HGM] Kludge to handle engines that send FRC-style castling
5618               when they shouldn't (like TSCP-Gothic) */
5619            switch(moveType) {
5620              case WhiteASideCastleFR:
5621              case BlackASideCastleFR:
5622                toX+=2;
5623                currentMoveString[2]++;
5624                break;
5625              case WhiteHSideCastleFR:
5626              case BlackHSideCastleFR:
5627                toX--;
5628                currentMoveString[2]--;
5629                break;
5630              default: ; // nothing to do, but suppresses warning of pedantic compilers
5631            }
5632         }
5633         hintRequested = FALSE;
5634         lastHint[0] = NULLCHAR;
5635         bookRequested = FALSE;
5636         /* Program may be pondering now */
5637         cps->maybeThinking = TRUE;
5638         if (cps->sendTime == 2) cps->sendTime = 1;
5639         if (cps->offeredDraw) cps->offeredDraw--;
5640
5641 #if ZIPPY
5642         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5643             first.initDone) {
5644           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5645           ics_user_moved = 1;
5646           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5647                 char buf[3*MSG_SIZ];
5648
5649                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %1.0f knps) PV=%s\n",
5650                         programStats.score / 100.,
5651                         programStats.depth,
5652                         programStats.time / 100.,
5653                         (unsigned int)programStats.nodes,
5654                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5655                         programStats.movelist);
5656                 SendToICS(buf);
5657           }
5658         }
5659 #endif
5660         /* currentMoveString is set as a side-effect of ParseOneMove */
5661         strcpy(machineMove, currentMoveString);
5662         strcat(machineMove, "\n");
5663         strcpy(moveList[forwardMostMove], machineMove);
5664
5665         /* [AS] Save move info and clear stats for next move */
5666         pvInfoList[ forwardMostMove ].score = programStats.score;
5667         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5668         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5669         ClearProgramStats();
5670         thinkOutput[0] = NULLCHAR;
5671         hiddenThinkOutputState = 0;
5672
5673         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5674
5675         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5676         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5677             int count = 0;
5678
5679             while( count < adjudicateLossPlies ) {
5680                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5681
5682                 if( count & 1 ) {
5683                     score = -score; /* Flip score for winning side */
5684                 }
5685
5686                 if( score > adjudicateLossThreshold ) {
5687                     break;
5688                 }
5689
5690                 count++;
5691             }
5692
5693             if( count >= adjudicateLossPlies ) {
5694                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5695
5696                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5697                     "Xboard adjudication", 
5698                     GE_XBOARD );
5699
5700                 return;
5701             }
5702         }
5703
5704         if( gameMode == TwoMachinesPlay ) {
5705           // [HGM] some adjudications useful with buggy engines
5706             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5707           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5708
5709
5710             if( appData.testLegality )
5711             {   /* [HGM] Some more adjudications for obstinate engines */
5712                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5713                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5714                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5715                 static int moveCount = 6;
5716                 ChessMove result;
5717                 char *reason = NULL;
5718
5719                 /* Count what is on board. */
5720                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5721                 {   ChessSquare p = boards[forwardMostMove][i][j];
5722                     int m=i;
5723
5724                     switch((int) p)
5725                     {   /* count B,N,R and other of each side */
5726                         case WhiteKing:
5727                         case BlackKing:
5728                              NrK++; break; // [HGM] atomic: count Kings
5729                         case WhiteKnight:
5730                              NrWN++; break;
5731                         case WhiteBishop:
5732                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5733                              bishopsColor |= 1 << ((i^j)&1);
5734                              NrWB++; break;
5735                         case BlackKnight:
5736                              NrBN++; break;
5737                         case BlackBishop:
5738                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5739                              bishopsColor |= 1 << ((i^j)&1);
5740                              NrBB++; break;
5741                         case WhiteRook:
5742                              NrWR++; break;
5743                         case BlackRook:
5744                              NrBR++; break;
5745                         case WhiteQueen:
5746                              NrWQ++; break;
5747                         case BlackQueen:
5748                              NrBQ++; break;
5749                         case EmptySquare: 
5750                              break;
5751                         case BlackPawn:
5752                              m = 7-i;
5753                         case WhitePawn:
5754                              PawnAdvance += m; NrPawns++;
5755                     }
5756                     NrPieces += (p != EmptySquare);
5757                     NrW += ((int)p < (int)BlackPawn);
5758                     if(gameInfo.variant == VariantXiangqi && 
5759                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5760                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5761                         NrW -= ((int)p < (int)BlackPawn);
5762                     }
5763                 }
5764
5765                 /* Some material-based adjudications that have to be made before stalemate test */
5766                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5767                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5768                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5769                      if(appData.checkMates) {
5770                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5771                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5772                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
5773                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
5774                          return;
5775                      }
5776                 }
5777
5778                 /* Bare King in Shatranj (loses) or Losers (wins) */
5779                 if( NrW == 1 || NrPieces - NrW == 1) {
5780                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5781                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
5782                      if(appData.checkMates) {
5783                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5784                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5785                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5786                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5787                          return;
5788                      }
5789                   } else
5790                   if( gameInfo.variant == VariantShatranj && --bare < 0)
5791                   {    /* bare King */
5792                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5793                         if(appData.checkMates) {
5794                             /* but only adjudicate if adjudication enabled */
5795                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5796                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5797                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
5798                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5799                             return;
5800                         }
5801                   }
5802                 } else bare = 1;
5803
5804
5805             // don't wait for engine to announce game end if we can judge ourselves
5806             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5807                                        castlingRights[forwardMostMove]) ) {
5808               case MT_CHECK:
5809                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5810                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
5811                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5812                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5813                             checkCnt++;
5814                         if(checkCnt >= 2) {
5815                             reason = "Xboard adjudication: 3rd check";
5816                             epStatus[forwardMostMove] = EP_CHECKMATE;
5817                             break;
5818                         }
5819                     }
5820                 }
5821               case MT_NONE:
5822               default:
5823                 break;
5824               case MT_STALEMATE:
5825               case MT_STAINMATE:
5826                 reason = "Xboard adjudication: Stalemate";
5827                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5828                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
5829                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5830                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
5831                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5832                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5833                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5834                                                                         EP_CHECKMATE : EP_WINS);
5835                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5836                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5837                 }
5838                 break;
5839               case MT_CHECKMATE:
5840                 reason = "Xboard adjudication: Checkmate";
5841                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5842                 break;
5843             }
5844
5845                 switch(i = epStatus[forwardMostMove]) {
5846                     case EP_STALEMATE:
5847                         result = GameIsDrawn; break;
5848                     case EP_CHECKMATE:
5849                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5850                     case EP_WINS:
5851                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5852                     default:
5853                         result = (ChessMove) 0;
5854                 }
5855                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5856                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5857                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5858                     GameEnds( result, reason, GE_XBOARD );
5859                     return;
5860                 }
5861
5862                 /* Next absolutely insufficient mating material. */
5863                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
5864                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5865                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5866                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5867                 {    /* KBK, KNK, KK of KBKB with like Bishops */
5868
5869                      /* always flag draws, for judging claims */
5870                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
5871
5872                      if(appData.materialDraws) {
5873                          /* but only adjudicate them if adjudication enabled */
5874                          SendToProgram("force\n", cps->other); // suppress reply
5875                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5876                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5877                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5878                          return;
5879                      }
5880                 }
5881
5882                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5883                 if(NrPieces == 4 && 
5884                    (   NrWR == 1 && NrBR == 1 /* KRKR */
5885                    || NrWQ==1 && NrBQ==1     /* KQKQ */
5886                    || NrWN==2 || NrBN==2     /* KNNK */
5887                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5888                   ) ) {
5889                      if(--moveCount < 0 && appData.trivialDraws)
5890                      {    /* if the first 3 moves do not show a tactical win, declare draw */
5891                           SendToProgram("force\n", cps->other); // suppress reply
5892                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5893                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5894                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5895                           return;
5896                      }
5897                 } else moveCount = 6;
5898             }
5899           }
5900 #if 1
5901     if (appData.debugMode) { int i;
5902       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5903               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5904               appData.drawRepeats);
5905       for( i=forwardMostMove; i>=backwardMostMove; i-- )
5906            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5907
5908     }
5909 #endif
5910                 /* Check for rep-draws */
5911                 count = 0;
5912                 for(k = forwardMostMove-2;
5913                     k>=backwardMostMove && k>=forwardMostMove-100 &&
5914                         epStatus[k] < EP_UNKNOWN &&
5915                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5916                     k-=2)
5917                 {   int rights=0;
5918 #if 0
5919     if (appData.debugMode) {
5920       fprintf(debugFP, " loop\n");
5921     }
5922 #endif
5923                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
5924 #if 0
5925     if (appData.debugMode) {
5926       fprintf(debugFP, "match\n");
5927     }
5928 #endif
5929                         /* compare castling rights */
5930                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5931                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5932                                 rights++; /* King lost rights, while rook still had them */
5933                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5934                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5935                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5936                                    rights++; /* but at least one rook lost them */
5937                         }
5938                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5939                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5940                                 rights++; 
5941                         if( castlingRights[forwardMostMove][5] >= 0 ) {
5942                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5943                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5944                                    rights++;
5945                         }
5946 #if 0
5947     if (appData.debugMode) {
5948       for(i=0; i<nrCastlingRights; i++)
5949       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
5950     }
5951
5952     if (appData.debugMode) {
5953       fprintf(debugFP, " %d %d\n", rights, k);
5954     }
5955 #endif
5956                         if( rights == 0 && ++count > appData.drawRepeats-2
5957                             && appData.drawRepeats > 1) {
5958                              /* adjudicate after user-specified nr of repeats */
5959                              SendToProgram("force\n", cps->other); // suppress reply
5960                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5961                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5962                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
5963                                 // [HGM] xiangqi: check for forbidden perpetuals
5964                                 int m, ourPerpetual = 1, hisPerpetual = 1;
5965                                 for(m=forwardMostMove; m>k; m-=2) {
5966                                     if(MateTest(boards[m], PosFlags(m), 
5967                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
5968                                         ourPerpetual = 0; // the current mover did not always check
5969                                     if(MateTest(boards[m-1], PosFlags(m-1), 
5970                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
5971                                         hisPerpetual = 0; // the opponent did not always check
5972                                 }
5973                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
5974                                                                         ourPerpetual, hisPerpetual);
5975                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
5976                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5977                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
5978                                     return;
5979                                 }
5980                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
5981                                     break; // (or we would have caught him before). Abort repetition-checking loop.
5982                                 // Now check for perpetual chases
5983                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
5984                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
5985                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
5986                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
5987                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5988                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
5989                                         return;
5990                                     }
5991                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
5992                                         break; // Abort repetition-checking loop.
5993                                 }
5994                                 // if neither of us is checking or chasing all the time, or both are, it is draw
5995                              }
5996                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
5997                              return;
5998                         }
5999                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6000                              epStatus[forwardMostMove] = EP_REP_DRAW;
6001                     }
6002                 }
6003
6004                 /* Now we test for 50-move draws. Determine ply count */
6005                 count = forwardMostMove;
6006                 /* look for last irreversble move */
6007                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6008                     count--;
6009                 /* if we hit starting position, add initial plies */
6010                 if( count == backwardMostMove )
6011                     count -= initialRulePlies;
6012                 count = forwardMostMove - count; 
6013                 if( count >= 100)
6014                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6015                          /* this is used to judge if draw claims are legal */
6016                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6017                          SendToProgram("force\n", cps->other); // suppress reply
6018                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6019                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6020                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6021                          return;
6022                 }
6023
6024                 /* if draw offer is pending, treat it as a draw claim
6025                  * when draw condition present, to allow engines a way to
6026                  * claim draws before making their move to avoid a race
6027                  * condition occurring after their move
6028                  */
6029                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6030                          char *p = NULL;
6031                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6032                              p = "Draw claim: 50-move rule";
6033                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6034                              p = "Draw claim: 3-fold repetition";
6035                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6036                              p = "Draw claim: insufficient mating material";
6037                          if( p != NULL ) {
6038                              SendToProgram("force\n", cps->other); // suppress reply
6039                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6040                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6041                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6042                              return;
6043                          }
6044                 }
6045
6046
6047                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6048                     SendToProgram("force\n", cps->other); // suppress reply
6049                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6050                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6051
6052                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6053
6054                     return;
6055                 }
6056         }
6057
6058         bookHit = NULL;
6059         if (gameMode == TwoMachinesPlay) {
6060             /* [HGM] relaying draw offers moved to after reception of move */
6061             /* and interpreting offer as claim if it brings draw condition */
6062             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6063                 SendToProgram("draw\n", cps->other);
6064             }
6065             if (cps->other->sendTime) {
6066                 SendTimeRemaining(cps->other,
6067                                   cps->other->twoMachinesColor[0] == 'w');
6068             }
6069             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6070             if (firstMove && !bookHit) {
6071                 firstMove = FALSE;
6072                 if (cps->other->useColors) {
6073                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6074                 }
6075                 SendToProgram("go\n", cps->other);
6076             }
6077             cps->other->maybeThinking = TRUE;
6078         }
6079
6080         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6081         
6082         if (!pausing && appData.ringBellAfterMoves) {
6083             RingBell();
6084         }
6085
6086         /* 
6087          * Reenable menu items that were disabled while
6088          * machine was thinking
6089          */
6090         if (gameMode != TwoMachinesPlay)
6091             SetUserThinkingEnables();
6092
6093         // [HGM] book: after book hit opponent has received move and is now in force mode
6094         // force the book reply into it, and then fake that it outputted this move by jumping
6095         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6096         if(bookHit) {
6097                 static char bookMove[MSG_SIZ]; // a bit generous?
6098
6099                 strcpy(bookMove, "move ");
6100                 strcat(bookMove, bookHit);
6101                 message = bookMove;
6102                 cps = cps->other;
6103                 programStats.nodes = programStats.depth = programStats.time = 
6104                 programStats.score = programStats.got_only_move = 0;
6105                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6106
6107                 if(cps->lastPing != cps->lastPong) {
6108                     savedMessage = message; // args for deferred call
6109                     savedState = cps;
6110                     ScheduleDelayedEvent(DeferredBookMove, 10);
6111                     return;
6112                 }
6113                 goto FakeBookMove;
6114         }
6115
6116         return;
6117     }
6118
6119     /* Set special modes for chess engines.  Later something general
6120      *  could be added here; for now there is just one kludge feature,
6121      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6122      *  when "xboard" is given as an interactive command.
6123      */
6124     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6125         cps->useSigint = FALSE;
6126         cps->useSigterm = FALSE;
6127     }
6128     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6129       ParseFeatures(message+8, cps);
6130       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6131     }
6132
6133     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6134      * want this, I was asked to put it in, and obliged.
6135      */
6136     if (!strncmp(message, "setboard ", 9)) {
6137         Board initial_position; int i;
6138
6139         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6140
6141         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6142             DisplayError(_("Bad FEN received from engine"), 0);
6143             return ;
6144         } else {
6145            Reset(FALSE, FALSE);
6146            CopyBoard(boards[0], initial_position);
6147            initialRulePlies = FENrulePlies;
6148            epStatus[0] = FENepStatus;
6149            for( i=0; i<nrCastlingRights; i++ )
6150                 castlingRights[0][i] = FENcastlingRights[i];
6151            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6152            else gameMode = MachinePlaysBlack;                 
6153            DrawPosition(FALSE, boards[currentMove]);
6154         }
6155         return;
6156     }
6157
6158     /*
6159      * Look for communication commands
6160      */
6161     if (!strncmp(message, "telluser ", 9)) {
6162         DisplayNote(message + 9);
6163         return;
6164     }
6165     if (!strncmp(message, "tellusererror ", 14)) {
6166         DisplayError(message + 14, 0);
6167         return;
6168     }
6169     if (!strncmp(message, "tellopponent ", 13)) {
6170       if (appData.icsActive) {
6171         if (loggedOn) {
6172           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6173           SendToICS(buf1);
6174         }
6175       } else {
6176         DisplayNote(message + 13);
6177       }
6178       return;
6179     }
6180     if (!strncmp(message, "tellothers ", 11)) {
6181       if (appData.icsActive) {
6182         if (loggedOn) {
6183           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6184           SendToICS(buf1);
6185         }
6186       }
6187       return;
6188     }
6189     if (!strncmp(message, "tellall ", 8)) {
6190       if (appData.icsActive) {
6191         if (loggedOn) {
6192           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6193           SendToICS(buf1);
6194         }
6195       } else {
6196         DisplayNote(message + 8);
6197       }
6198       return;
6199     }
6200     if (strncmp(message, "warning", 7) == 0) {
6201         /* Undocumented feature, use tellusererror in new code */
6202         DisplayError(message, 0);
6203         return;
6204     }
6205     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6206         strcpy(realname, cps->tidy);
6207         strcat(realname, " query");
6208         AskQuestion(realname, buf2, buf1, cps->pr);
6209         return;
6210     }
6211     /* Commands from the engine directly to ICS.  We don't allow these to be 
6212      *  sent until we are logged on. Crafty kibitzes have been known to 
6213      *  interfere with the login process.
6214      */
6215     if (loggedOn) {
6216         if (!strncmp(message, "tellics ", 8)) {
6217             SendToICS(message + 8);
6218             SendToICS("\n");
6219             return;
6220         }
6221         if (!strncmp(message, "tellicsnoalias ", 15)) {
6222             SendToICS(ics_prefix);
6223             SendToICS(message + 15);
6224             SendToICS("\n");
6225             return;
6226         }
6227         /* The following are for backward compatibility only */
6228         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6229             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6230             SendToICS(ics_prefix);
6231             SendToICS(message);
6232             SendToICS("\n");
6233             return;
6234         }
6235     }
6236     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6237         return;
6238     }
6239     /*
6240      * If the move is illegal, cancel it and redraw the board.
6241      * Also deal with other error cases.  Matching is rather loose
6242      * here to accommodate engines written before the spec.
6243      */
6244     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6245         strncmp(message, "Error", 5) == 0) {
6246         if (StrStr(message, "name") || 
6247             StrStr(message, "rating") || StrStr(message, "?") ||
6248             StrStr(message, "result") || StrStr(message, "board") ||
6249             StrStr(message, "bk") || StrStr(message, "computer") ||
6250             StrStr(message, "variant") || StrStr(message, "hint") ||
6251             StrStr(message, "random") || StrStr(message, "depth") ||
6252             StrStr(message, "accepted")) {
6253             return;
6254         }
6255         if (StrStr(message, "protover")) {
6256           /* Program is responding to input, so it's apparently done
6257              initializing, and this error message indicates it is
6258              protocol version 1.  So we don't need to wait any longer
6259              for it to initialize and send feature commands. */
6260           FeatureDone(cps, 1);
6261           cps->protocolVersion = 1;
6262           return;
6263         }
6264         cps->maybeThinking = FALSE;
6265
6266         if (StrStr(message, "draw")) {
6267             /* Program doesn't have "draw" command */
6268             cps->sendDrawOffers = 0;
6269             return;
6270         }
6271         if (cps->sendTime != 1 &&
6272             (StrStr(message, "time") || StrStr(message, "otim"))) {
6273           /* Program apparently doesn't have "time" or "otim" command */
6274           cps->sendTime = 0;
6275           return;
6276         }
6277         if (StrStr(message, "analyze")) {
6278             cps->analysisSupport = FALSE;
6279             cps->analyzing = FALSE;
6280             Reset(FALSE, TRUE);
6281             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6282             DisplayError(buf2, 0);
6283             return;
6284         }
6285         if (StrStr(message, "(no matching move)st")) {
6286           /* Special kludge for GNU Chess 4 only */
6287           cps->stKludge = TRUE;
6288           SendTimeControl(cps, movesPerSession, timeControl,
6289                           timeIncrement, appData.searchDepth,
6290                           searchTime);
6291           return;
6292         }
6293         if (StrStr(message, "(no matching move)sd")) {
6294           /* Special kludge for GNU Chess 4 only */
6295           cps->sdKludge = TRUE;
6296           SendTimeControl(cps, movesPerSession, timeControl,
6297                           timeIncrement, appData.searchDepth,
6298                           searchTime);
6299           return;
6300         }
6301         if (!StrStr(message, "llegal")) {
6302             return;
6303         }
6304         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6305             gameMode == IcsIdle) return;
6306         if (forwardMostMove <= backwardMostMove) return;
6307 #if 0
6308         /* Following removed: it caused a bug where a real illegal move
6309            message in analyze mored would be ignored. */
6310         if (cps == &first && programStats.ok_to_send == 0) {
6311             /* Bogus message from Crafty responding to "."  This filtering
6312                can miss some of the bad messages, but fortunately the bug 
6313                is fixed in current Crafty versions, so it doesn't matter. */
6314             return;
6315         }
6316 #endif
6317         if (pausing) PauseEvent();
6318         if (gameMode == PlayFromGameFile) {
6319             /* Stop reading this game file */
6320             gameMode = EditGame;
6321             ModeHighlight();
6322         }
6323         currentMove = --forwardMostMove;
6324         DisplayMove(currentMove-1); /* before DisplayMoveError */
6325         SwitchClocks();
6326         DisplayBothClocks();
6327         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6328                 parseList[currentMove], cps->which);
6329         DisplayMoveError(buf1);
6330         DrawPosition(FALSE, boards[currentMove]);
6331
6332         /* [HGM] illegal-move claim should forfeit game when Xboard */
6333         /* only passes fully legal moves                            */
6334         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6335             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6336                                 "False illegal-move claim", GE_XBOARD );
6337         }
6338         return;
6339     }
6340     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6341         /* Program has a broken "time" command that
6342            outputs a string not ending in newline.
6343            Don't use it. */
6344         cps->sendTime = 0;
6345     }
6346     
6347     /*
6348      * If chess program startup fails, exit with an error message.
6349      * Attempts to recover here are futile.
6350      */
6351     if ((StrStr(message, "unknown host") != NULL)
6352         || (StrStr(message, "No remote directory") != NULL)
6353         || (StrStr(message, "not found") != NULL)
6354         || (StrStr(message, "No such file") != NULL)
6355         || (StrStr(message, "can't alloc") != NULL)
6356         || (StrStr(message, "Permission denied") != NULL)) {
6357
6358         cps->maybeThinking = FALSE;
6359         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6360                 cps->which, cps->program, cps->host, message);
6361         RemoveInputSource(cps->isr);
6362         DisplayFatalError(buf1, 0, 1);
6363         return;
6364     }
6365     
6366     /* 
6367      * Look for hint output
6368      */
6369     if (sscanf(message, "Hint: %s", buf1) == 1) {
6370         if (cps == &first && hintRequested) {
6371             hintRequested = FALSE;
6372             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6373                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6374                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6375                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6376                                     fromY, fromX, toY, toX, promoChar, buf1);
6377                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6378                 DisplayInformation(buf2);
6379             } else {
6380                 /* Hint move could not be parsed!? */
6381               snprintf(buf2, sizeof(buf2),
6382                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6383                         buf1, cps->which);
6384                 DisplayError(buf2, 0);
6385             }
6386         } else {
6387             strcpy(lastHint, buf1);
6388         }
6389         return;
6390     }
6391
6392     /*
6393      * Ignore other messages if game is not in progress
6394      */
6395     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6396         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6397
6398     /*
6399      * look for win, lose, draw, or draw offer
6400      */
6401     if (strncmp(message, "1-0", 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         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6412         return;
6413     } else if (strncmp(message, "0-1", 3) == 0) {
6414         char *p, *q, *r = "";
6415         p = strchr(message, '{');
6416         if (p) {
6417             q = strchr(p, '}');
6418             if (q) {
6419                 *q = NULLCHAR;
6420                 r = p + 1;
6421             }
6422         }
6423         /* Kludge for Arasan 4.1 bug */
6424         if (strcmp(r, "Black resigns") == 0) {
6425             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6426             return;
6427         }
6428         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6429         return;
6430     } else if (strncmp(message, "1/2", 3) == 0) {
6431         char *p, *q, *r = "";
6432         p = strchr(message, '{');
6433         if (p) {
6434             q = strchr(p, '}');
6435             if (q) {
6436                 *q = NULLCHAR;
6437                 r = p + 1;
6438             }
6439         }
6440             
6441         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6442         return;
6443
6444     } else if (strncmp(message, "White resign", 12) == 0) {
6445         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6446         return;
6447     } else if (strncmp(message, "Black resign", 12) == 0) {
6448         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6449         return;
6450     } else if (strncmp(message, "White matches", 13) == 0 ||
6451                strncmp(message, "Black matches", 13) == 0   ) {
6452         /* [HGM] ignore GNUShogi noises */
6453         return;
6454     } else if (strncmp(message, "White", 5) == 0 &&
6455                message[5] != '(' &&
6456                StrStr(message, "Black") == NULL) {
6457         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6458         return;
6459     } else if (strncmp(message, "Black", 5) == 0 &&
6460                message[5] != '(') {
6461         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6462         return;
6463     } else if (strcmp(message, "resign") == 0 ||
6464                strcmp(message, "computer resigns") == 0) {
6465         switch (gameMode) {
6466           case MachinePlaysBlack:
6467           case IcsPlayingBlack:
6468             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6469             break;
6470           case MachinePlaysWhite:
6471           case IcsPlayingWhite:
6472             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6473             break;
6474           case TwoMachinesPlay:
6475             if (cps->twoMachinesColor[0] == 'w')
6476               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6477             else
6478               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6479             break;
6480           default:
6481             /* can't happen */
6482             break;
6483         }
6484         return;
6485     } else if (strncmp(message, "opponent mates", 14) == 0) {
6486         switch (gameMode) {
6487           case MachinePlaysBlack:
6488           case IcsPlayingBlack:
6489             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6490             break;
6491           case MachinePlaysWhite:
6492           case IcsPlayingWhite:
6493             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6494             break;
6495           case TwoMachinesPlay:
6496             if (cps->twoMachinesColor[0] == 'w')
6497               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6498             else
6499               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6500             break;
6501           default:
6502             /* can't happen */
6503             break;
6504         }
6505         return;
6506     } else if (strncmp(message, "computer mates", 14) == 0) {
6507         switch (gameMode) {
6508           case MachinePlaysBlack:
6509           case IcsPlayingBlack:
6510             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6511             break;
6512           case MachinePlaysWhite:
6513           case IcsPlayingWhite:
6514             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6515             break;
6516           case TwoMachinesPlay:
6517             if (cps->twoMachinesColor[0] == 'w')
6518               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6519             else
6520               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6521             break;
6522           default:
6523             /* can't happen */
6524             break;
6525         }
6526         return;
6527     } else if (strncmp(message, "checkmate", 9) == 0) {
6528         if (WhiteOnMove(forwardMostMove)) {
6529             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6530         } else {
6531             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6532         }
6533         return;
6534     } else if (strstr(message, "Draw") != NULL ||
6535                strstr(message, "game is a draw") != NULL) {
6536         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6537         return;
6538     } else if (strstr(message, "offer") != NULL &&
6539                strstr(message, "draw") != NULL) {
6540 #if ZIPPY
6541         if (appData.zippyPlay && first.initDone) {
6542             /* Relay offer to ICS */
6543             SendToICS(ics_prefix);
6544             SendToICS("draw\n");
6545         }
6546 #endif
6547         cps->offeredDraw = 2; /* valid until this engine moves twice */
6548         if (gameMode == TwoMachinesPlay) {
6549             if (cps->other->offeredDraw) {
6550                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6551             /* [HGM] in two-machine mode we delay relaying draw offer      */
6552             /* until after we also have move, to see if it is really claim */
6553             }
6554 #if 0
6555               else {
6556                 if (cps->other->sendDrawOffers) {
6557                     SendToProgram("draw\n", cps->other);
6558                 }
6559             }
6560 #endif
6561         } else if (gameMode == MachinePlaysWhite ||
6562                    gameMode == MachinePlaysBlack) {
6563           if (userOfferedDraw) {
6564             DisplayInformation(_("Machine accepts your draw offer"));
6565             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6566           } else {
6567             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6568           }
6569         }
6570     }
6571
6572     
6573     /*
6574      * Look for thinking output
6575      */
6576     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6577           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6578                                 ) {
6579         int plylev, mvleft, mvtot, curscore, time;
6580         char mvname[MOVE_LEN];
6581         u64 nodes; // [DM]
6582         char plyext;
6583         int ignore = FALSE;
6584         int prefixHint = FALSE;
6585         mvname[0] = NULLCHAR;
6586
6587         switch (gameMode) {
6588           case MachinePlaysBlack:
6589           case IcsPlayingBlack:
6590             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6591             break;
6592           case MachinePlaysWhite:
6593           case IcsPlayingWhite:
6594             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6595             break;
6596           case AnalyzeMode:
6597           case AnalyzeFile:
6598             break;
6599           case IcsObserving: /* [DM] icsEngineAnalyze */
6600             if (!appData.icsEngineAnalyze) ignore = TRUE;
6601             break;
6602           case TwoMachinesPlay:
6603             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6604                 ignore = TRUE;
6605             }
6606             break;
6607           default:
6608             ignore = TRUE;
6609             break;
6610         }
6611
6612         if (!ignore) {
6613             buf1[0] = NULLCHAR;
6614             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6615                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6616
6617                 if (plyext != ' ' && plyext != '\t') {
6618                     time *= 100;
6619                 }
6620
6621                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6622                 if( cps->scoreIsAbsolute && 
6623                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6624                 {
6625                     curscore = -curscore;
6626                 }
6627
6628
6629                 programStats.depth = plylev;
6630                 programStats.nodes = nodes;
6631                 programStats.time = time;
6632                 programStats.score = curscore;
6633                 programStats.got_only_move = 0;
6634
6635                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6636                         int ticklen;
6637
6638                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6639                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6640                         if(WhiteOnMove(forwardMostMove)) 
6641                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6642                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6643                 }
6644
6645                 /* Buffer overflow protection */
6646                 if (buf1[0] != NULLCHAR) {
6647                     if (strlen(buf1) >= sizeof(programStats.movelist)
6648                         && appData.debugMode) {
6649                         fprintf(debugFP,
6650                                 "PV is too long; using the first %d bytes.\n",
6651                                 sizeof(programStats.movelist) - 1);
6652                     }
6653
6654                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6655                 } else {
6656                     sprintf(programStats.movelist, " no PV\n");
6657                 }
6658
6659                 if (programStats.seen_stat) {
6660                     programStats.ok_to_send = 1;
6661                 }
6662
6663                 if (strchr(programStats.movelist, '(') != NULL) {
6664                     programStats.line_is_book = 1;
6665                     programStats.nr_moves = 0;
6666                     programStats.moves_left = 0;
6667                 } else {
6668                     programStats.line_is_book = 0;
6669                 }
6670
6671                 SendProgramStatsToFrontend( cps, &programStats );
6672
6673                 /* 
6674                     [AS] Protect the thinkOutput buffer from overflow... this
6675                     is only useful if buf1 hasn't overflowed first!
6676                 */
6677                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6678                         plylev, 
6679                         (gameMode == TwoMachinesPlay ?
6680                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6681                         ((double) curscore) / 100.0,
6682                         prefixHint ? lastHint : "",
6683                         prefixHint ? " " : "" );
6684
6685                 if( buf1[0] != NULLCHAR ) {
6686                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6687
6688                     if( strlen(buf1) > max_len ) {
6689                         if( appData.debugMode) {
6690                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6691                         }
6692                         buf1[max_len+1] = '\0';
6693                     }
6694
6695                     strcat( thinkOutput, buf1 );
6696                 }
6697
6698                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6699                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6700                     DisplayMove(currentMove - 1);
6701                     DisplayAnalysis();
6702                 }
6703                 return;
6704
6705             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6706                 /* crafty (9.25+) says "(only move) <move>"
6707                  * if there is only 1 legal move
6708                  */
6709                 sscanf(p, "(only move) %s", buf1);
6710                 sprintf(thinkOutput, "%s (only move)", buf1);
6711                 sprintf(programStats.movelist, "%s (only move)", buf1);
6712                 programStats.depth = 1;
6713                 programStats.nr_moves = 1;
6714                 programStats.moves_left = 1;
6715                 programStats.nodes = 1;
6716                 programStats.time = 1;
6717                 programStats.got_only_move = 1;
6718
6719                 /* Not really, but we also use this member to
6720                    mean "line isn't going to change" (Crafty
6721                    isn't searching, so stats won't change) */
6722                 programStats.line_is_book = 1;
6723
6724                 SendProgramStatsToFrontend( cps, &programStats );
6725                 
6726                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
6727                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6728                     DisplayMove(currentMove - 1);
6729                     DisplayAnalysis();
6730                 }
6731                 return;
6732             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6733                               &time, &nodes, &plylev, &mvleft,
6734                               &mvtot, mvname) >= 5) {
6735                 /* The stat01: line is from Crafty (9.29+) in response
6736                    to the "." command */
6737                 programStats.seen_stat = 1;
6738                 cps->maybeThinking = TRUE;
6739
6740                 if (programStats.got_only_move || !appData.periodicUpdates)
6741                   return;
6742
6743                 programStats.depth = plylev;
6744                 programStats.time = time;
6745                 programStats.nodes = nodes;
6746                 programStats.moves_left = mvleft;
6747                 programStats.nr_moves = mvtot;
6748                 strcpy(programStats.move_name, mvname);
6749                 programStats.ok_to_send = 1;
6750                 programStats.movelist[0] = '\0';
6751
6752                 SendProgramStatsToFrontend( cps, &programStats );
6753
6754                 DisplayAnalysis();
6755                 return;
6756
6757             } else if (strncmp(message,"++",2) == 0) {
6758                 /* Crafty 9.29+ outputs this */
6759                 programStats.got_fail = 2;
6760                 return;
6761
6762             } else if (strncmp(message,"--",2) == 0) {
6763                 /* Crafty 9.29+ outputs this */
6764                 programStats.got_fail = 1;
6765                 return;
6766
6767             } else if (thinkOutput[0] != NULLCHAR &&
6768                        strncmp(message, "    ", 4) == 0) {
6769                 unsigned message_len;
6770
6771                 p = message;
6772                 while (*p && *p == ' ') p++;
6773
6774                 message_len = strlen( p );
6775
6776                 /* [AS] Avoid buffer overflow */
6777                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6778                     strcat(thinkOutput, " ");
6779                     strcat(thinkOutput, p);
6780                 }
6781
6782                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6783                     strcat(programStats.movelist, " ");
6784                     strcat(programStats.movelist, p);
6785                 }
6786
6787                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6788                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6789                     DisplayMove(currentMove - 1);
6790                     DisplayAnalysis();
6791                 }
6792                 return;
6793             }
6794         }
6795         else {
6796             buf1[0] = NULLCHAR;
6797
6798             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6799                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
6800             {
6801                 ChessProgramStats cpstats;
6802
6803                 if (plyext != ' ' && plyext != '\t') {
6804                     time *= 100;
6805                 }
6806
6807                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6808                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6809                     curscore = -curscore;
6810                 }
6811
6812                 cpstats.depth = plylev;
6813                 cpstats.nodes = nodes;
6814                 cpstats.time = time;
6815                 cpstats.score = curscore;
6816                 cpstats.got_only_move = 0;
6817                 cpstats.movelist[0] = '\0';
6818
6819                 if (buf1[0] != NULLCHAR) {
6820                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6821                 }
6822
6823                 cpstats.ok_to_send = 0;
6824                 cpstats.line_is_book = 0;
6825                 cpstats.nr_moves = 0;
6826                 cpstats.moves_left = 0;
6827
6828                 SendProgramStatsToFrontend( cps, &cpstats );
6829             }
6830         }
6831     }
6832 }
6833
6834
6835 /* Parse a game score from the character string "game", and
6836    record it as the history of the current game.  The game
6837    score is NOT assumed to start from the standard position. 
6838    The display is not updated in any way.
6839    */
6840 void
6841 ParseGameHistory(game)
6842      char *game;
6843 {
6844     ChessMove moveType;
6845     int fromX, fromY, toX, toY, boardIndex;
6846     char promoChar;
6847     char *p, *q;
6848     char buf[MSG_SIZ];
6849
6850     if (appData.debugMode)
6851       fprintf(debugFP, "Parsing game history: %s\n", game);
6852
6853     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6854     gameInfo.site = StrSave(appData.icsHost);
6855     gameInfo.date = PGNDate();
6856     gameInfo.round = StrSave("-");
6857
6858     /* Parse out names of players */
6859     while (*game == ' ') game++;
6860     p = buf;
6861     while (*game != ' ') *p++ = *game++;
6862     *p = NULLCHAR;
6863     gameInfo.white = StrSave(buf);
6864     while (*game == ' ') game++;
6865     p = buf;
6866     while (*game != ' ' && *game != '\n') *p++ = *game++;
6867     *p = NULLCHAR;
6868     gameInfo.black = StrSave(buf);
6869
6870     /* Parse moves */
6871     boardIndex = blackPlaysFirst ? 1 : 0;
6872     yynewstr(game);
6873     for (;;) {
6874         yyboardindex = boardIndex;
6875         moveType = (ChessMove) yylex();
6876         switch (moveType) {
6877           case IllegalMove:             /* maybe suicide chess, etc. */
6878   if (appData.debugMode) {
6879     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6880     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6881     setbuf(debugFP, NULL);
6882   }
6883           case WhitePromotionChancellor:
6884           case BlackPromotionChancellor:
6885           case WhitePromotionArchbishop:
6886           case BlackPromotionArchbishop:
6887           case WhitePromotionQueen:
6888           case BlackPromotionQueen:
6889           case WhitePromotionRook:
6890           case BlackPromotionRook:
6891           case WhitePromotionBishop:
6892           case BlackPromotionBishop:
6893           case WhitePromotionKnight:
6894           case BlackPromotionKnight:
6895           case WhitePromotionKing:
6896           case BlackPromotionKing:
6897           case NormalMove:
6898           case WhiteCapturesEnPassant:
6899           case BlackCapturesEnPassant:
6900           case WhiteKingSideCastle:
6901           case WhiteQueenSideCastle:
6902           case BlackKingSideCastle:
6903           case BlackQueenSideCastle:
6904           case WhiteKingSideCastleWild:
6905           case WhiteQueenSideCastleWild:
6906           case BlackKingSideCastleWild:
6907           case BlackQueenSideCastleWild:
6908           /* PUSH Fabien */
6909           case WhiteHSideCastleFR:
6910           case WhiteASideCastleFR:
6911           case BlackHSideCastleFR:
6912           case BlackASideCastleFR:
6913           /* POP Fabien */
6914             fromX = currentMoveString[0] - AAA;
6915             fromY = currentMoveString[1] - ONE;
6916             toX = currentMoveString[2] - AAA;
6917             toY = currentMoveString[3] - ONE;
6918             promoChar = currentMoveString[4];
6919             break;
6920           case WhiteDrop:
6921           case BlackDrop:
6922             fromX = moveType == WhiteDrop ?
6923               (int) CharToPiece(ToUpper(currentMoveString[0])) :
6924             (int) CharToPiece(ToLower(currentMoveString[0]));
6925             fromY = DROP_RANK;
6926             toX = currentMoveString[2] - AAA;
6927             toY = currentMoveString[3] - ONE;
6928             promoChar = NULLCHAR;
6929             break;
6930           case AmbiguousMove:
6931             /* bug? */
6932             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6933   if (appData.debugMode) {
6934     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6935     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6936     setbuf(debugFP, NULL);
6937   }
6938             DisplayError(buf, 0);
6939             return;
6940           case ImpossibleMove:
6941             /* bug? */
6942             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6943   if (appData.debugMode) {
6944     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6945     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6946     setbuf(debugFP, NULL);
6947   }
6948             DisplayError(buf, 0);
6949             return;
6950           case (ChessMove) 0:   /* end of file */
6951             if (boardIndex < backwardMostMove) {
6952                 /* Oops, gap.  How did that happen? */
6953                 DisplayError(_("Gap in move list"), 0);
6954                 return;
6955             }
6956             backwardMostMove =  blackPlaysFirst ? 1 : 0;
6957             if (boardIndex > forwardMostMove) {
6958                 forwardMostMove = boardIndex;
6959             }
6960             return;
6961           case ElapsedTime:
6962             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
6963                 strcat(parseList[boardIndex-1], " ");
6964                 strcat(parseList[boardIndex-1], yy_text);
6965             }
6966             continue;
6967           case Comment:
6968           case PGNTag:
6969           case NAG:
6970           default:
6971             /* ignore */
6972             continue;
6973           case WhiteWins:
6974           case BlackWins:
6975           case GameIsDrawn:
6976           case GameUnfinished:
6977             if (gameMode == IcsExamining) {
6978                 if (boardIndex < backwardMostMove) {
6979                     /* Oops, gap.  How did that happen? */
6980                     return;
6981                 }
6982                 backwardMostMove = blackPlaysFirst ? 1 : 0;
6983                 return;
6984             }
6985             gameInfo.result = moveType;
6986             p = strchr(yy_text, '{');
6987             if (p == NULL) p = strchr(yy_text, '(');
6988             if (p == NULL) {
6989                 p = yy_text;
6990                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
6991             } else {
6992                 q = strchr(p, *p == '{' ? '}' : ')');
6993                 if (q != NULL) *q = NULLCHAR;
6994                 p++;
6995             }
6996             gameInfo.resultDetails = StrSave(p);
6997             continue;
6998         }
6999         if (boardIndex >= forwardMostMove &&
7000             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7001             backwardMostMove = blackPlaysFirst ? 1 : 0;
7002             return;
7003         }
7004         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7005                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7006                                  parseList[boardIndex]);
7007         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7008         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7009         /* currentMoveString is set as a side-effect of yylex */
7010         strcpy(moveList[boardIndex], currentMoveString);
7011         strcat(moveList[boardIndex], "\n");
7012         boardIndex++;
7013         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7014                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7015         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7016                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7017           case MT_NONE:
7018           case MT_STALEMATE:
7019           default:
7020             break;
7021           case MT_CHECK:
7022             if(gameInfo.variant != VariantShogi)
7023                 strcat(parseList[boardIndex - 1], "+");
7024             break;
7025           case MT_CHECKMATE:
7026           case MT_STAINMATE:
7027             strcat(parseList[boardIndex - 1], "#");
7028             break;
7029         }
7030     }
7031 }
7032
7033
7034 /* Apply a move to the given board  */
7035 void
7036 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7037      int fromX, fromY, toX, toY;
7038      int promoChar;
7039      Board board;
7040      char *castling;
7041      char *ep;
7042 {
7043   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7044
7045     /* [HGM] compute & store e.p. status and castling rights for new position */
7046     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7047     { int i;
7048
7049       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7050       oldEP = *ep;
7051       *ep = EP_NONE;
7052
7053       if( board[toY][toX] != EmptySquare ) 
7054            *ep = EP_CAPTURE;  
7055
7056       if( board[fromY][fromX] == WhitePawn ) {
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] == BlackPawn &&
7061                         gameInfo.variant != VariantBerolina || toX < fromX)
7062                       *ep = toX | berolina;
7063                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7064                         gameInfo.variant != VariantBerolina || toX > fromX) 
7065                       *ep = toX;
7066            }
7067       } else 
7068       if( board[fromY][fromX] == BlackPawn ) {
7069            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7070                *ep = EP_PAWN_MOVE; 
7071            if( toY-fromY== -2) {
7072                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7073                         gameInfo.variant != VariantBerolina || toX < fromX)
7074                       *ep = toX | berolina;
7075                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7076                         gameInfo.variant != VariantBerolina || toX > fromX) 
7077                       *ep = toX;
7078            }
7079        }
7080
7081        for(i=0; i<nrCastlingRights; i++) {
7082            if(castling[i] == fromX && castlingRank[i] == fromY ||
7083               castling[i] == toX   && castlingRank[i] == toY   
7084              ) castling[i] = -1; // revoke for moved or captured piece
7085        }
7086
7087     }
7088
7089   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7090   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7091        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7092          
7093   if (fromX == toX && fromY == toY) return;
7094
7095   if (fromY == DROP_RANK) {
7096         /* must be first */
7097         piece = board[toY][toX] = (ChessSquare) fromX;
7098   } else {
7099      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7100      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7101      if(gameInfo.variant == VariantKnightmate)
7102          king += (int) WhiteUnicorn - (int) WhiteKing;
7103
7104     /* Code added by Tord: */
7105     /* FRC castling assumed when king captures friendly rook. */
7106     if (board[fromY][fromX] == WhiteKing &&
7107              board[toY][toX] == WhiteRook) {
7108       board[fromY][fromX] = EmptySquare;
7109       board[toY][toX] = EmptySquare;
7110       if(toX > fromX) {
7111         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7112       } else {
7113         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7114       }
7115     } else if (board[fromY][fromX] == BlackKing &&
7116                board[toY][toX] == BlackRook) {
7117       board[fromY][fromX] = EmptySquare;
7118       board[toY][toX] = EmptySquare;
7119       if(toX > fromX) {
7120         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7121       } else {
7122         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7123       }
7124     /* End of code added by Tord */
7125
7126     } else if (board[fromY][fromX] == king
7127         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7128         && toY == fromY && toX > fromX+1) {
7129         board[fromY][fromX] = EmptySquare;
7130         board[toY][toX] = king;
7131         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7132         board[fromY][BOARD_RGHT-1] = EmptySquare;
7133     } else if (board[fromY][fromX] == king
7134         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7135                && toY == fromY && toX < fromX-1) {
7136         board[fromY][fromX] = EmptySquare;
7137         board[toY][toX] = king;
7138         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7139         board[fromY][BOARD_LEFT] = EmptySquare;
7140     } else if (board[fromY][fromX] == WhitePawn
7141                && toY == BOARD_HEIGHT-1
7142                && gameInfo.variant != VariantXiangqi
7143                ) {
7144         /* white pawn promotion */
7145         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7146         if (board[toY][toX] == EmptySquare) {
7147             board[toY][toX] = WhiteQueen;
7148         }
7149         if(gameInfo.variant==VariantBughouse ||
7150            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7151             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7152         board[fromY][fromX] = EmptySquare;
7153     } else if ((fromY == BOARD_HEIGHT-4)
7154                && (toX != fromX)
7155                && gameInfo.variant != VariantXiangqi
7156                && gameInfo.variant != VariantBerolina
7157                && (board[fromY][fromX] == WhitePawn)
7158                && (board[toY][toX] == EmptySquare)) {
7159         board[fromY][fromX] = EmptySquare;
7160         board[toY][toX] = WhitePawn;
7161         captured = board[toY - 1][toX];
7162         board[toY - 1][toX] = EmptySquare;
7163     } else if ((fromY == BOARD_HEIGHT-4)
7164                && (toX == fromX)
7165                && gameInfo.variant == VariantBerolina
7166                && (board[fromY][fromX] == WhitePawn)
7167                && (board[toY][toX] == EmptySquare)) {
7168         board[fromY][fromX] = EmptySquare;
7169         board[toY][toX] = WhitePawn;
7170         if(oldEP & EP_BEROLIN_A) {
7171                 captured = board[fromY][fromX-1];
7172                 board[fromY][fromX-1] = EmptySquare;
7173         }else{  captured = board[fromY][fromX+1];
7174                 board[fromY][fromX+1] = EmptySquare;
7175         }
7176     } else if (board[fromY][fromX] == king
7177         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7178                && toY == fromY && toX > fromX+1) {
7179         board[fromY][fromX] = EmptySquare;
7180         board[toY][toX] = king;
7181         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7182         board[fromY][BOARD_RGHT-1] = EmptySquare;
7183     } else if (board[fromY][fromX] == king
7184         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7185                && toY == fromY && toX < fromX-1) {
7186         board[fromY][fromX] = EmptySquare;
7187         board[toY][toX] = king;
7188         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7189         board[fromY][BOARD_LEFT] = EmptySquare;
7190     } else if (fromY == 7 && fromX == 3
7191                && board[fromY][fromX] == BlackKing
7192                && toY == 7 && toX == 5) {
7193         board[fromY][fromX] = EmptySquare;
7194         board[toY][toX] = BlackKing;
7195         board[fromY][7] = EmptySquare;
7196         board[toY][4] = BlackRook;
7197     } else if (fromY == 7 && fromX == 3
7198                && board[fromY][fromX] == BlackKing
7199                && toY == 7 && toX == 1) {
7200         board[fromY][fromX] = EmptySquare;
7201         board[toY][toX] = BlackKing;
7202         board[fromY][0] = EmptySquare;
7203         board[toY][2] = BlackRook;
7204     } else if (board[fromY][fromX] == BlackPawn
7205                && toY == 0
7206                && gameInfo.variant != VariantXiangqi
7207                ) {
7208         /* black pawn promotion */
7209         board[0][toX] = CharToPiece(ToLower(promoChar));
7210         if (board[0][toX] == EmptySquare) {
7211             board[0][toX] = BlackQueen;
7212         }
7213         if(gameInfo.variant==VariantBughouse ||
7214            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7215             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7216         board[fromY][fromX] = EmptySquare;
7217     } else if ((fromY == 3)
7218                && (toX != fromX)
7219                && gameInfo.variant != VariantXiangqi
7220                && gameInfo.variant != VariantBerolina
7221                && (board[fromY][fromX] == BlackPawn)
7222                && (board[toY][toX] == EmptySquare)) {
7223         board[fromY][fromX] = EmptySquare;
7224         board[toY][toX] = BlackPawn;
7225         captured = board[toY + 1][toX];
7226         board[toY + 1][toX] = EmptySquare;
7227     } else if ((fromY == 3)
7228                && (toX == fromX)
7229                && gameInfo.variant == VariantBerolina
7230                && (board[fromY][fromX] == BlackPawn)
7231                && (board[toY][toX] == EmptySquare)) {
7232         board[fromY][fromX] = EmptySquare;
7233         board[toY][toX] = BlackPawn;
7234         if(oldEP & EP_BEROLIN_A) {
7235                 captured = board[fromY][fromX-1];
7236                 board[fromY][fromX-1] = EmptySquare;
7237         }else{  captured = board[fromY][fromX+1];
7238                 board[fromY][fromX+1] = EmptySquare;
7239         }
7240     } else {
7241         board[toY][toX] = board[fromY][fromX];
7242         board[fromY][fromX] = EmptySquare;
7243     }
7244
7245     /* [HGM] now we promote for Shogi, if needed */
7246     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7247         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7248   }
7249
7250     if (gameInfo.holdingsWidth != 0) {
7251
7252       /* !!A lot more code needs to be written to support holdings  */
7253       /* [HGM] OK, so I have written it. Holdings are stored in the */
7254       /* penultimate board files, so they are automaticlly stored   */
7255       /* in the game history.                                       */
7256       if (fromY == DROP_RANK) {
7257         /* Delete from holdings, by decreasing count */
7258         /* and erasing image if necessary            */
7259         p = (int) fromX;
7260         if(p < (int) BlackPawn) { /* white drop */
7261              p -= (int)WhitePawn;
7262              if(p >= gameInfo.holdingsSize) p = 0;
7263              if(--board[p][BOARD_WIDTH-2] == 0)
7264                   board[p][BOARD_WIDTH-1] = EmptySquare;
7265         } else {                  /* black drop */
7266              p -= (int)BlackPawn;
7267              if(p >= gameInfo.holdingsSize) p = 0;
7268              if(--board[BOARD_HEIGHT-1-p][1] == 0)
7269                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7270         }
7271       }
7272       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7273           && gameInfo.variant != VariantBughouse        ) {
7274         /* [HGM] holdings: Add to holdings, if holdings exist */
7275         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7276                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7277                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7278         }
7279         p = (int) captured;
7280         if (p >= (int) BlackPawn) {
7281           p -= (int)BlackPawn;
7282           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7283                   /* in Shogi restore piece to its original  first */
7284                   captured = (ChessSquare) (DEMOTED captured);
7285                   p = DEMOTED p;
7286           }
7287           p = PieceToNumber((ChessSquare)p);
7288           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7289           board[p][BOARD_WIDTH-2]++;
7290           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7291         } else {
7292           p -= (int)WhitePawn;
7293           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7294                   captured = (ChessSquare) (DEMOTED captured);
7295                   p = DEMOTED p;
7296           }
7297           p = PieceToNumber((ChessSquare)p);
7298           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7299           board[BOARD_HEIGHT-1-p][1]++;
7300           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7301         }
7302       }
7303
7304     } else if (gameInfo.variant == VariantAtomic) {
7305       if (captured != EmptySquare) {
7306         int y, x;
7307         for (y = toY-1; y <= toY+1; y++) {
7308           for (x = toX-1; x <= toX+1; x++) {
7309             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7310                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7311               board[y][x] = EmptySquare;
7312             }
7313           }
7314         }
7315         board[toY][toX] = EmptySquare;
7316       }
7317     }
7318     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7319         /* [HGM] Shogi promotions */
7320         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7321     }
7322
7323     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7324                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7325         // [HGM] superchess: take promotion piece out of holdings
7326         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7327         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7328             if(!--board[k][BOARD_WIDTH-2])
7329                 board[k][BOARD_WIDTH-1] = EmptySquare;
7330         } else {
7331             if(!--board[BOARD_HEIGHT-1-k][1])
7332                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7333         }
7334     }
7335
7336 }
7337
7338 /* Updates forwardMostMove */
7339 void
7340 MakeMove(fromX, fromY, toX, toY, promoChar)
7341      int fromX, fromY, toX, toY;
7342      int promoChar;
7343 {
7344 //    forwardMostMove++; // [HGM] bare: moved downstream
7345
7346     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7347         int timeLeft; static int lastLoadFlag=0; int king, piece;
7348         piece = boards[forwardMostMove][fromY][fromX];
7349         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7350         if(gameInfo.variant == VariantKnightmate)
7351             king += (int) WhiteUnicorn - (int) WhiteKing;
7352         if(forwardMostMove == 0) {
7353             if(blackPlaysFirst) 
7354                 fprintf(serverMoves, "%s;", second.tidy);
7355             fprintf(serverMoves, "%s;", first.tidy);
7356             if(!blackPlaysFirst) 
7357                 fprintf(serverMoves, "%s;", second.tidy);
7358         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7359         lastLoadFlag = loadFlag;
7360         // print base move
7361         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7362         // print castling suffix
7363         if( toY == fromY && piece == king ) {
7364             if(toX-fromX > 1)
7365                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7366             if(fromX-toX >1)
7367                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7368         }
7369         // e.p. suffix
7370         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7371              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7372              boards[forwardMostMove][toY][toX] == EmptySquare
7373              && fromX != toX )
7374                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7375         // promotion suffix
7376         if(promoChar != NULLCHAR)
7377                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7378         if(!loadFlag) {
7379             fprintf(serverMoves, "/%d/%d",
7380                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7381             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7382             else                      timeLeft = blackTimeRemaining/1000;
7383             fprintf(serverMoves, "/%d", timeLeft);
7384         }
7385         fflush(serverMoves);
7386     }
7387
7388     if (forwardMostMove+1 >= MAX_MOVES) {
7389       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7390                         0, 1);
7391       return;
7392     }
7393     SwitchClocks();
7394     timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
7395     timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
7396     if (commentList[forwardMostMove+1] != NULL) {
7397         free(commentList[forwardMostMove+1]);
7398         commentList[forwardMostMove+1] = NULL;
7399     }
7400     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7401     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7402     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7403                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7404     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7405     gameInfo.result = GameUnfinished;
7406     if (gameInfo.resultDetails != NULL) {
7407         free(gameInfo.resultDetails);
7408         gameInfo.resultDetails = NULL;
7409     }
7410     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7411                               moveList[forwardMostMove - 1]);
7412     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7413                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7414                              fromY, fromX, toY, toX, promoChar,
7415                              parseList[forwardMostMove - 1]);
7416     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7417                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7418                             castlingRights[forwardMostMove]) ) {
7419       case MT_NONE:
7420       case MT_STALEMATE:
7421       default:
7422         break;
7423       case MT_CHECK:
7424         if(gameInfo.variant != VariantShogi)
7425             strcat(parseList[forwardMostMove - 1], "+");
7426         break;
7427       case MT_CHECKMATE:
7428       case MT_STAINMATE:
7429         strcat(parseList[forwardMostMove - 1], "#");
7430         break;
7431     }
7432     if (appData.debugMode) {
7433         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7434     }
7435
7436 }
7437
7438 /* Updates currentMove if not pausing */
7439 void
7440 ShowMove(fromX, fromY, toX, toY)
7441 {
7442     int instant = (gameMode == PlayFromGameFile) ?
7443         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7444     if(appData.noGUI) return;
7445     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7446         if (!instant) {
7447             if (forwardMostMove == currentMove + 1) {
7448                 AnimateMove(boards[forwardMostMove - 1],
7449                             fromX, fromY, toX, toY);
7450             }
7451             if (appData.highlightLastMove) {
7452                 SetHighlights(fromX, fromY, toX, toY);
7453             }
7454         }
7455         currentMove = forwardMostMove;
7456     }
7457
7458     if (instant) return;
7459
7460     DisplayMove(currentMove - 1);
7461     DrawPosition(FALSE, boards[currentMove]);
7462     DisplayBothClocks();
7463     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7464 }
7465
7466 void SendEgtPath(ChessProgramState *cps)
7467 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7468         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7469
7470         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7471
7472         while(*p) {
7473             char c, *q = name+1, *r, *s;
7474
7475             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7476             while(*p && *p != ',') *q++ = *p++;
7477             *q++ = ':'; *q = 0;
7478             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7479                 strcmp(name, ",nalimov:") == 0 ) {
7480                 // take nalimov path from the menu-changeable option first, if it is defined
7481                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7482                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7483             } else
7484             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7485                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7486                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7487                 s = r = StrStr(s, ":") + 1; // beginning of path info
7488                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7489                 c = *r; *r = 0;             // temporarily null-terminate path info
7490                     *--q = 0;               // strip of trailig ':' from name
7491                     sprintf(buf, "egtbpath %s %s\n", name+1, s);
7492                 *r = c;
7493                 SendToProgram(buf,cps);     // send egtbpath command for this format
7494             }
7495             if(*p == ',') p++; // read away comma to position for next format name
7496         }
7497 }
7498
7499 void
7500 InitChessProgram(cps, setup)
7501      ChessProgramState *cps;
7502      int setup; /* [HGM] needed to setup FRC opening position */
7503 {
7504     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7505     if (appData.noChessProgram) return;
7506     hintRequested = FALSE;
7507     bookRequested = FALSE;
7508
7509     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7510     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7511     if(cps->memSize) { /* [HGM] memory */
7512         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7513         SendToProgram(buf, cps);
7514     }
7515     SendEgtPath(cps); /* [HGM] EGT */
7516     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7517         sprintf(buf, "cores %d\n", appData.smpCores);
7518         SendToProgram(buf, cps);
7519     }
7520
7521     SendToProgram(cps->initString, cps);
7522     if (gameInfo.variant != VariantNormal &&
7523         gameInfo.variant != VariantLoadable
7524         /* [HGM] also send variant if board size non-standard */
7525         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7526                                             ) {
7527       char *v = VariantName(gameInfo.variant);
7528       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7529         /* [HGM] in protocol 1 we have to assume all variants valid */
7530         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7531         DisplayFatalError(buf, 0, 1);
7532         return;
7533       }
7534
7535       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7536       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7537       if( gameInfo.variant == VariantXiangqi )
7538            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7539       if( gameInfo.variant == VariantShogi )
7540            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7541       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7542            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7543       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7544                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7545            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7546       if( gameInfo.variant == VariantCourier )
7547            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7548       if( gameInfo.variant == VariantSuper )
7549            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7550       if( gameInfo.variant == VariantGreat )
7551            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7552
7553       if(overruled) {
7554            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7555                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7556            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7557            if(StrStr(cps->variants, b) == NULL) { 
7558                // specific sized variant not known, check if general sizing allowed
7559                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7560                    if(StrStr(cps->variants, "boardsize") == NULL) {
7561                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7562                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7563                        DisplayFatalError(buf, 0, 1);
7564                        return;
7565                    }
7566                    /* [HGM] here we really should compare with the maximum supported board size */
7567                }
7568            }
7569       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7570       sprintf(buf, "variant %s\n", b);
7571       SendToProgram(buf, cps);
7572     }
7573     currentlyInitializedVariant = gameInfo.variant;
7574
7575     /* [HGM] send opening position in FRC to first engine */
7576     if(setup) {
7577           SendToProgram("force\n", cps);
7578           SendBoard(cps, 0);
7579           /* engine is now in force mode! Set flag to wake it up after first move. */
7580           setboardSpoiledMachineBlack = 1;
7581     }
7582
7583     if (cps->sendICS) {
7584       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7585       SendToProgram(buf, cps);
7586     }
7587     cps->maybeThinking = FALSE;
7588     cps->offeredDraw = 0;
7589     if (!appData.icsActive) {
7590         SendTimeControl(cps, movesPerSession, timeControl,
7591                         timeIncrement, appData.searchDepth,
7592                         searchTime);
7593     }
7594     if (appData.showThinking 
7595         // [HGM] thinking: four options require thinking output to be sent
7596         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7597                                 ) {
7598         SendToProgram("post\n", cps);
7599     }
7600     SendToProgram("hard\n", cps);
7601     if (!appData.ponderNextMove) {
7602         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7603            it without being sure what state we are in first.  "hard"
7604            is not a toggle, so that one is OK.
7605          */
7606         SendToProgram("easy\n", cps);
7607     }
7608     if (cps->usePing) {
7609       sprintf(buf, "ping %d\n", ++cps->lastPing);
7610       SendToProgram(buf, cps);
7611     }
7612     cps->initDone = TRUE;
7613 }   
7614
7615
7616 void
7617 StartChessProgram(cps)
7618      ChessProgramState *cps;
7619 {
7620     char buf[MSG_SIZ];
7621     int err;
7622
7623     if (appData.noChessProgram) return;
7624     cps->initDone = FALSE;
7625
7626     if (strcmp(cps->host, "localhost") == 0) {
7627         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7628     } else if (*appData.remoteShell == NULLCHAR) {
7629         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7630     } else {
7631         if (*appData.remoteUser == NULLCHAR) {
7632           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7633                     cps->program);
7634         } else {
7635           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7636                     cps->host, appData.remoteUser, cps->program);
7637         }
7638         err = StartChildProcess(buf, "", &cps->pr);
7639     }
7640     
7641     if (err != 0) {
7642         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7643         DisplayFatalError(buf, err, 1);
7644         cps->pr = NoProc;
7645         cps->isr = NULL;
7646         return;
7647     }
7648     
7649     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7650     if (cps->protocolVersion > 1) {
7651       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7652       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7653       cps->comboCnt = 0;  //                and values of combo boxes
7654       SendToProgram(buf, cps);
7655     } else {
7656       SendToProgram("xboard\n", cps);
7657     }
7658 }
7659
7660
7661 void
7662 TwoMachinesEventIfReady P((void))
7663 {
7664   if (first.lastPing != first.lastPong) {
7665     DisplayMessage("", _("Waiting for first chess program"));
7666     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7667     return;
7668   }
7669   if (second.lastPing != second.lastPong) {
7670     DisplayMessage("", _("Waiting for second chess program"));
7671     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7672     return;
7673   }
7674   ThawUI();
7675   TwoMachinesEvent();
7676 }
7677
7678 void
7679 NextMatchGame P((void))
7680 {
7681     int index; /* [HGM] autoinc: step lod index during match */
7682     Reset(FALSE, TRUE);
7683     if (*appData.loadGameFile != NULLCHAR) {
7684         index = appData.loadGameIndex;
7685         if(index < 0) { // [HGM] autoinc
7686             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7687             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7688         } 
7689         LoadGameFromFile(appData.loadGameFile,
7690                          index,
7691                          appData.loadGameFile, FALSE);
7692     } else if (*appData.loadPositionFile != NULLCHAR) {
7693         index = appData.loadPositionIndex;
7694         if(index < 0) { // [HGM] autoinc
7695             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7696             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7697         } 
7698         LoadPositionFromFile(appData.loadPositionFile,
7699                              index,
7700                              appData.loadPositionFile);
7701     }
7702     TwoMachinesEventIfReady();
7703 }
7704
7705 void UserAdjudicationEvent( int result )
7706 {
7707     ChessMove gameResult = GameIsDrawn;
7708
7709     if( result > 0 ) {
7710         gameResult = WhiteWins;
7711     }
7712     else if( result < 0 ) {
7713         gameResult = BlackWins;
7714     }
7715
7716     if( gameMode == TwoMachinesPlay ) {
7717         GameEnds( gameResult, "User adjudication", GE_XBOARD );
7718     }
7719 }
7720
7721
7722 // [HGM] save: calculate checksum of game to make games easily identifiable
7723 int StringCheckSum(char *s)
7724 {
7725         int i = 0;
7726         if(s==NULL) return 0;
7727         while(*s) i = i*259 + *s++;
7728         return i;
7729 }
7730
7731 int GameCheckSum()
7732 {
7733         int i, sum=0;
7734         for(i=backwardMostMove; i<forwardMostMove; i++) {
7735                 sum += pvInfoList[i].depth;
7736                 sum += StringCheckSum(parseList[i]);
7737                 sum += StringCheckSum(commentList[i]);
7738                 sum *= 261;
7739         }
7740         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7741         return sum + StringCheckSum(commentList[i]);
7742 } // end of save patch
7743
7744 void
7745 GameEnds(result, resultDetails, whosays)
7746      ChessMove result;
7747      char *resultDetails;
7748      int whosays;
7749 {
7750     GameMode nextGameMode;
7751     int isIcsGame;
7752     char buf[MSG_SIZ];
7753
7754     if(endingGame) return; /* [HGM] crash: forbid recursion */
7755     endingGame = 1;
7756
7757     if (appData.debugMode) {
7758       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7759               result, resultDetails ? resultDetails : "(null)", whosays);
7760     }
7761
7762     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7763         /* If we are playing on ICS, the server decides when the
7764            game is over, but the engine can offer to draw, claim 
7765            a draw, or resign. 
7766          */
7767 #if ZIPPY
7768         if (appData.zippyPlay && first.initDone) {
7769             if (result == GameIsDrawn) {
7770                 /* In case draw still needs to be claimed */
7771                 SendToICS(ics_prefix);
7772                 SendToICS("draw\n");
7773             } else if (StrCaseStr(resultDetails, "resign")) {
7774                 SendToICS(ics_prefix);
7775                 SendToICS("resign\n");
7776             }
7777         }
7778 #endif
7779         endingGame = 0; /* [HGM] crash */
7780         return;
7781     }
7782
7783     /* If we're loading the game from a file, stop */
7784     if (whosays == GE_FILE) {
7785       (void) StopLoadGameTimer();
7786       gameFileFP = NULL;
7787     }
7788
7789     /* Cancel draw offers */
7790     first.offeredDraw = second.offeredDraw = 0;
7791
7792     /* If this is an ICS game, only ICS can really say it's done;
7793        if not, anyone can. */
7794     isIcsGame = (gameMode == IcsPlayingWhite || 
7795                  gameMode == IcsPlayingBlack || 
7796                  gameMode == IcsObserving    || 
7797                  gameMode == IcsExamining);
7798
7799     if (!isIcsGame || whosays == GE_ICS) {
7800         /* OK -- not an ICS game, or ICS said it was done */
7801         StopClocks();
7802         if (!isIcsGame && !appData.noChessProgram) 
7803           SetUserThinkingEnables();
7804     
7805         /* [HGM] if a machine claims the game end we verify this claim */
7806         if(gameMode == TwoMachinesPlay && appData.testClaims) {
7807             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7808                 char claimer;
7809                 ChessMove trueResult = (ChessMove) -1;
7810
7811                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
7812                                             first.twoMachinesColor[0] :
7813                                             second.twoMachinesColor[0] ;
7814
7815                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7816                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7817                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7818                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7819                 } else
7820                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7821                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7822                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7823                 } else
7824                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7825                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7826                 }
7827
7828                 // now verify win claims, but not in drop games, as we don't understand those yet
7829                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7830                                                  || gameInfo.variant == VariantGreat) &&
7831                     (result == WhiteWins && claimer == 'w' ||
7832                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
7833                       if (appData.debugMode) {
7834                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
7835                                 result, epStatus[forwardMostMove], forwardMostMove);
7836                       }
7837                       if(result != trueResult) {
7838                               sprintf(buf, "False win claim: '%s'", resultDetails);
7839                               result = claimer == 'w' ? BlackWins : WhiteWins;
7840                               resultDetails = buf;
7841                       }
7842                 } else
7843                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7844                     && (forwardMostMove <= backwardMostMove ||
7845                         epStatus[forwardMostMove-1] > EP_DRAWS ||
7846                         (claimer=='b')==(forwardMostMove&1))
7847                                                                                   ) {
7848                       /* [HGM] verify: draws that were not flagged are false claims */
7849                       sprintf(buf, "False draw claim: '%s'", resultDetails);
7850                       result = claimer == 'w' ? BlackWins : WhiteWins;
7851                       resultDetails = buf;
7852                 }
7853                 /* (Claiming a loss is accepted no questions asked!) */
7854             }
7855             /* [HGM] bare: don't allow bare King to win */
7856             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7857                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
7858                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7859                && result != GameIsDrawn)
7860             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7861                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7862                         int p = (int)boards[forwardMostMove][i][j] - color;
7863                         if(p >= 0 && p <= (int)WhiteKing) k++;
7864                 }
7865                 if (appData.debugMode) {
7866                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7867                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7868                 }
7869                 if(k <= 1) {
7870                         result = GameIsDrawn;
7871                         sprintf(buf, "%s but bare king", resultDetails);
7872                         resultDetails = buf;
7873                 }
7874             }
7875         }
7876
7877
7878         if(serverMoves != NULL && !loadFlag) { char c = '=';
7879             if(result==WhiteWins) c = '+';
7880             if(result==BlackWins) c = '-';
7881             if(resultDetails != NULL)
7882                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7883         }
7884         if (resultDetails != NULL) {
7885             gameInfo.result = result;
7886             gameInfo.resultDetails = StrSave(resultDetails);
7887
7888             /* display last move only if game was not loaded from file */
7889             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7890                 DisplayMove(currentMove - 1);
7891     
7892             if (forwardMostMove != 0) {
7893                 if (gameMode != PlayFromGameFile && gameMode != EditGame
7894                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7895                                                                 ) {
7896                     if (*appData.saveGameFile != NULLCHAR) {
7897                         SaveGameToFile(appData.saveGameFile, TRUE);
7898                     } else if (appData.autoSaveGames) {
7899                         AutoSaveGame();
7900                     }
7901                     if (*appData.savePositionFile != NULLCHAR) {
7902                         SavePositionToFile(appData.savePositionFile);
7903                     }
7904                 }
7905             }
7906
7907             /* Tell program how game ended in case it is learning */
7908             /* [HGM] Moved this to after saving the PGN, just in case */
7909             /* engine died and we got here through time loss. In that */
7910             /* case we will get a fatal error writing the pipe, which */
7911             /* would otherwise lose us the PGN.                       */
7912             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
7913             /* output during GameEnds should never be fatal anymore   */
7914             if (gameMode == MachinePlaysWhite ||
7915                 gameMode == MachinePlaysBlack ||
7916                 gameMode == TwoMachinesPlay ||
7917                 gameMode == IcsPlayingWhite ||
7918                 gameMode == IcsPlayingBlack ||
7919                 gameMode == BeginningOfGame) {
7920                 char buf[MSG_SIZ];
7921                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7922                         resultDetails);
7923                 if (first.pr != NoProc) {
7924                     SendToProgram(buf, &first);
7925                 }
7926                 if (second.pr != NoProc &&
7927                     gameMode == TwoMachinesPlay) {
7928                     SendToProgram(buf, &second);
7929                 }
7930             }
7931         }
7932
7933         if (appData.icsActive) {
7934             if (appData.quietPlay &&
7935                 (gameMode == IcsPlayingWhite ||
7936                  gameMode == IcsPlayingBlack)) {
7937                 SendToICS(ics_prefix);
7938                 SendToICS("set shout 1\n");
7939             }
7940             nextGameMode = IcsIdle;
7941             ics_user_moved = FALSE;
7942             /* clean up premove.  It's ugly when the game has ended and the
7943              * premove highlights are still on the board.
7944              */
7945             if (gotPremove) {
7946               gotPremove = FALSE;
7947               ClearPremoveHighlights();
7948               DrawPosition(FALSE, boards[currentMove]);
7949             }
7950             if (whosays == GE_ICS) {
7951                 switch (result) {
7952                 case WhiteWins:
7953                     if (gameMode == IcsPlayingWhite)
7954                         PlayIcsWinSound();
7955                     else if(gameMode == IcsPlayingBlack)
7956                         PlayIcsLossSound();
7957                     break;
7958                 case BlackWins:
7959                     if (gameMode == IcsPlayingBlack)
7960                         PlayIcsWinSound();
7961                     else if(gameMode == IcsPlayingWhite)
7962                         PlayIcsLossSound();
7963                     break;
7964                 case GameIsDrawn:
7965                     PlayIcsDrawSound();
7966                     break;
7967                 default:
7968                     PlayIcsUnfinishedSound();
7969                 }
7970             }
7971         } else if (gameMode == EditGame ||
7972                    gameMode == PlayFromGameFile || 
7973                    gameMode == AnalyzeMode || 
7974                    gameMode == AnalyzeFile) {
7975             nextGameMode = gameMode;
7976         } else {
7977             nextGameMode = EndOfGame;
7978         }
7979         pausing = FALSE;
7980         ModeHighlight();
7981     } else {
7982         nextGameMode = gameMode;
7983     }
7984
7985     if (appData.noChessProgram) {
7986         gameMode = nextGameMode;
7987         ModeHighlight();
7988         endingGame = 0; /* [HGM] crash */
7989         return;
7990     }
7991
7992     if (first.reuse) {
7993         /* Put first chess program into idle state */
7994         if (first.pr != NoProc &&
7995             (gameMode == MachinePlaysWhite ||
7996              gameMode == MachinePlaysBlack ||
7997              gameMode == TwoMachinesPlay ||
7998              gameMode == IcsPlayingWhite ||
7999              gameMode == IcsPlayingBlack ||
8000              gameMode == BeginningOfGame)) {
8001             SendToProgram("force\n", &first);
8002             if (first.usePing) {
8003               char buf[MSG_SIZ];
8004               sprintf(buf, "ping %d\n", ++first.lastPing);
8005               SendToProgram(buf, &first);
8006             }
8007         }
8008     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8009         /* Kill off first chess program */
8010         if (first.isr != NULL)
8011           RemoveInputSource(first.isr);
8012         first.isr = NULL;
8013     
8014         if (first.pr != NoProc) {
8015             ExitAnalyzeMode();
8016             DoSleep( appData.delayBeforeQuit );
8017             SendToProgram("quit\n", &first);
8018             DoSleep( appData.delayAfterQuit );
8019             DestroyChildProcess(first.pr, first.useSigterm);
8020         }
8021         first.pr = NoProc;
8022     }
8023     if (second.reuse) {
8024         /* Put second chess program into idle state */
8025         if (second.pr != NoProc &&
8026             gameMode == TwoMachinesPlay) {
8027             SendToProgram("force\n", &second);
8028             if (second.usePing) {
8029               char buf[MSG_SIZ];
8030               sprintf(buf, "ping %d\n", ++second.lastPing);
8031               SendToProgram(buf, &second);
8032             }
8033         }
8034     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8035         /* Kill off second chess program */
8036         if (second.isr != NULL)
8037           RemoveInputSource(second.isr);
8038         second.isr = NULL;
8039     
8040         if (second.pr != NoProc) {
8041             DoSleep( appData.delayBeforeQuit );
8042             SendToProgram("quit\n", &second);
8043             DoSleep( appData.delayAfterQuit );
8044             DestroyChildProcess(second.pr, second.useSigterm);
8045         }
8046         second.pr = NoProc;
8047     }
8048
8049     if (matchMode && gameMode == TwoMachinesPlay) {
8050         switch (result) {
8051         case WhiteWins:
8052           if (first.twoMachinesColor[0] == 'w') {
8053             first.matchWins++;
8054           } else {
8055             second.matchWins++;
8056           }
8057           break;
8058         case BlackWins:
8059           if (first.twoMachinesColor[0] == 'b') {
8060             first.matchWins++;
8061           } else {
8062             second.matchWins++;
8063           }
8064           break;
8065         default:
8066           break;
8067         }
8068         if (matchGame < appData.matchGames) {
8069             char *tmp;
8070             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8071                 tmp = first.twoMachinesColor;
8072                 first.twoMachinesColor = second.twoMachinesColor;
8073                 second.twoMachinesColor = tmp;
8074             }
8075             gameMode = nextGameMode;
8076             matchGame++;
8077             if(appData.matchPause>10000 || appData.matchPause<10)
8078                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8079             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8080             endingGame = 0; /* [HGM] crash */
8081             return;
8082         } else {
8083             char buf[MSG_SIZ];
8084             gameMode = nextGameMode;
8085             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8086                     first.tidy, second.tidy,
8087                     first.matchWins, second.matchWins,
8088                     appData.matchGames - (first.matchWins + second.matchWins));
8089             DisplayFatalError(buf, 0, 0);
8090         }
8091     }
8092     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8093         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8094       ExitAnalyzeMode();
8095     gameMode = nextGameMode;
8096     ModeHighlight();
8097     endingGame = 0;  /* [HGM] crash */
8098 }
8099
8100 /* Assumes program was just initialized (initString sent).
8101    Leaves program in force mode. */
8102 void
8103 FeedMovesToProgram(cps, upto) 
8104      ChessProgramState *cps;
8105      int upto;
8106 {
8107     int i;
8108     
8109     if (appData.debugMode)
8110       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8111               startedFromSetupPosition ? "position and " : "",
8112               backwardMostMove, upto, cps->which);
8113     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8114         // [HGM] variantswitch: make engine aware of new variant
8115         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8116                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8117         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8118         SendToProgram(buf, cps);
8119         currentlyInitializedVariant = gameInfo.variant;
8120     }
8121     SendToProgram("force\n", cps);
8122     if (startedFromSetupPosition) {
8123         SendBoard(cps, backwardMostMove);
8124     if (appData.debugMode) {
8125         fprintf(debugFP, "feedMoves\n");
8126     }
8127     }
8128     for (i = backwardMostMove; i < upto; i++) {
8129         SendMoveToProgram(i, cps);
8130     }
8131 }
8132
8133
8134 void
8135 ResurrectChessProgram()
8136 {
8137      /* The chess program may have exited.
8138         If so, restart it and feed it all the moves made so far. */
8139
8140     if (appData.noChessProgram || first.pr != NoProc) return;
8141     
8142     StartChessProgram(&first);
8143     InitChessProgram(&first, FALSE);
8144     FeedMovesToProgram(&first, currentMove);
8145
8146     if (!first.sendTime) {
8147         /* can't tell gnuchess what its clock should read,
8148            so we bow to its notion. */
8149         ResetClocks();
8150         timeRemaining[0][currentMove] = whiteTimeRemaining;
8151         timeRemaining[1][currentMove] = blackTimeRemaining;
8152     }
8153
8154     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8155                 appData.icsEngineAnalyze) && first.analysisSupport) {
8156       SendToProgram("analyze\n", &first);
8157       first.analyzing = TRUE;
8158     }
8159 }
8160
8161 /*
8162  * Button procedures
8163  */
8164 void
8165 Reset(redraw, init)
8166      int redraw, init;
8167 {
8168     int i;
8169
8170     if (appData.debugMode) {
8171         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8172                 redraw, init, gameMode);
8173     }
8174     pausing = pauseExamInvalid = FALSE;
8175     startedFromSetupPosition = blackPlaysFirst = FALSE;
8176     firstMove = TRUE;
8177     whiteFlag = blackFlag = FALSE;
8178     userOfferedDraw = FALSE;
8179     hintRequested = bookRequested = FALSE;
8180     first.maybeThinking = FALSE;
8181     second.maybeThinking = FALSE;
8182     first.bookSuspend = FALSE; // [HGM] book
8183     second.bookSuspend = FALSE;
8184     thinkOutput[0] = NULLCHAR;
8185     lastHint[0] = NULLCHAR;
8186     ClearGameInfo(&gameInfo);
8187     gameInfo.variant = StringToVariant(appData.variant);
8188     ics_user_moved = ics_clock_paused = FALSE;
8189     ics_getting_history = H_FALSE;
8190     ics_gamenum = -1;
8191     white_holding[0] = black_holding[0] = NULLCHAR;
8192     ClearProgramStats();
8193     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8194     
8195     ResetFrontEnd();
8196     ClearHighlights();
8197     flipView = appData.flipView;
8198     ClearPremoveHighlights();
8199     gotPremove = FALSE;
8200     alarmSounded = FALSE;
8201
8202     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8203     if(appData.serverMovesName != NULL) {
8204         /* [HGM] prepare to make moves file for broadcasting */
8205         clock_t t = clock();
8206         if(serverMoves != NULL) fclose(serverMoves);
8207         serverMoves = fopen(appData.serverMovesName, "r");
8208         if(serverMoves != NULL) {
8209             fclose(serverMoves);
8210             /* delay 15 sec before overwriting, so all clients can see end */
8211             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8212         }
8213         serverMoves = fopen(appData.serverMovesName, "w");
8214     }
8215
8216     ExitAnalyzeMode();
8217     gameMode = BeginningOfGame;
8218     ModeHighlight();
8219     if(appData.icsActive) gameInfo.variant = VariantNormal;
8220     InitPosition(redraw);
8221     for (i = 0; i < MAX_MOVES; i++) {
8222         if (commentList[i] != NULL) {
8223             free(commentList[i]);
8224             commentList[i] = NULL;
8225         }
8226     }
8227     ResetClocks();
8228     timeRemaining[0][0] = whiteTimeRemaining;
8229     timeRemaining[1][0] = blackTimeRemaining;
8230     if (first.pr == NULL) {
8231         StartChessProgram(&first);
8232     }
8233     if (init) {
8234             InitChessProgram(&first, startedFromSetupPosition);
8235     }
8236     DisplayTitle("");
8237     DisplayMessage("", "");
8238     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8239     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8240 }
8241
8242 void
8243 AutoPlayGameLoop()
8244 {
8245     for (;;) {
8246         if (!AutoPlayOneMove())
8247           return;
8248         if (matchMode || appData.timeDelay == 0)
8249           continue;
8250         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8251           return;
8252         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8253         break;
8254     }
8255 }
8256
8257
8258 int
8259 AutoPlayOneMove()
8260 {
8261     int fromX, fromY, toX, toY;
8262
8263     if (appData.debugMode) {
8264       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8265     }
8266
8267     if (gameMode != PlayFromGameFile)
8268       return FALSE;
8269
8270     if (currentMove >= forwardMostMove) {
8271       gameMode = EditGame;
8272       ModeHighlight();
8273
8274       /* [AS] Clear current move marker at the end of a game */
8275       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8276
8277       return FALSE;
8278     }
8279     
8280     toX = moveList[currentMove][2] - AAA;
8281     toY = moveList[currentMove][3] - ONE;
8282
8283     if (moveList[currentMove][1] == '@') {
8284         if (appData.highlightLastMove) {
8285             SetHighlights(-1, -1, toX, toY);
8286         }
8287     } else {
8288         fromX = moveList[currentMove][0] - AAA;
8289         fromY = moveList[currentMove][1] - ONE;
8290
8291         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8292
8293         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8294
8295         if (appData.highlightLastMove) {
8296             SetHighlights(fromX, fromY, toX, toY);
8297         }
8298     }
8299     DisplayMove(currentMove);
8300     SendMoveToProgram(currentMove++, &first);
8301     DisplayBothClocks();
8302     DrawPosition(FALSE, boards[currentMove]);
8303     // [HGM] PV info: always display, routine tests if empty
8304     DisplayComment(currentMove - 1, commentList[currentMove]);
8305     return TRUE;
8306 }
8307
8308
8309 int
8310 LoadGameOneMove(readAhead)
8311      ChessMove readAhead;
8312 {
8313     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8314     char promoChar = NULLCHAR;
8315     ChessMove moveType;
8316     char move[MSG_SIZ];
8317     char *p, *q;
8318     
8319     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8320         gameMode != AnalyzeMode && gameMode != Training) {
8321         gameFileFP = NULL;
8322         return FALSE;
8323     }
8324     
8325     yyboardindex = forwardMostMove;
8326     if (readAhead != (ChessMove)0) {
8327       moveType = readAhead;
8328     } else {
8329       if (gameFileFP == NULL)
8330           return FALSE;
8331       moveType = (ChessMove) yylex();
8332     }
8333     
8334     done = FALSE;
8335     switch (moveType) {
8336       case Comment:
8337         if (appData.debugMode) 
8338           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8339         p = yy_text;
8340         if (*p == '{' || *p == '[' || *p == '(') {
8341             p[strlen(p) - 1] = NULLCHAR;
8342             p++;
8343         }
8344
8345         /* append the comment but don't display it */
8346         while (*p == '\n') p++;
8347         AppendComment(currentMove, p);
8348         return TRUE;
8349
8350       case WhiteCapturesEnPassant:
8351       case BlackCapturesEnPassant:
8352       case WhitePromotionChancellor:
8353       case BlackPromotionChancellor:
8354       case WhitePromotionArchbishop:
8355       case BlackPromotionArchbishop:
8356       case WhitePromotionCentaur:
8357       case BlackPromotionCentaur:
8358       case WhitePromotionQueen:
8359       case BlackPromotionQueen:
8360       case WhitePromotionRook:
8361       case BlackPromotionRook:
8362       case WhitePromotionBishop:
8363       case BlackPromotionBishop:
8364       case WhitePromotionKnight:
8365       case BlackPromotionKnight:
8366       case WhitePromotionKing:
8367       case BlackPromotionKing:
8368       case NormalMove:
8369       case WhiteKingSideCastle:
8370       case WhiteQueenSideCastle:
8371       case BlackKingSideCastle:
8372       case BlackQueenSideCastle:
8373       case WhiteKingSideCastleWild:
8374       case WhiteQueenSideCastleWild:
8375       case BlackKingSideCastleWild:
8376       case BlackQueenSideCastleWild:
8377       /* PUSH Fabien */
8378       case WhiteHSideCastleFR:
8379       case WhiteASideCastleFR:
8380       case BlackHSideCastleFR:
8381       case BlackASideCastleFR:
8382       /* POP Fabien */
8383         if (appData.debugMode)
8384           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8385         fromX = currentMoveString[0] - AAA;
8386         fromY = currentMoveString[1] - ONE;
8387         toX = currentMoveString[2] - AAA;
8388         toY = currentMoveString[3] - ONE;
8389         promoChar = currentMoveString[4];
8390         break;
8391
8392       case WhiteDrop:
8393       case BlackDrop:
8394         if (appData.debugMode)
8395           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8396         fromX = moveType == WhiteDrop ?
8397           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8398         (int) CharToPiece(ToLower(currentMoveString[0]));
8399         fromY = DROP_RANK;
8400         toX = currentMoveString[2] - AAA;
8401         toY = currentMoveString[3] - ONE;
8402         break;
8403
8404       case WhiteWins:
8405       case BlackWins:
8406       case GameIsDrawn:
8407       case GameUnfinished:
8408         if (appData.debugMode)
8409           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8410         p = strchr(yy_text, '{');
8411         if (p == NULL) p = strchr(yy_text, '(');
8412         if (p == NULL) {
8413             p = yy_text;
8414             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8415         } else {
8416             q = strchr(p, *p == '{' ? '}' : ')');
8417             if (q != NULL) *q = NULLCHAR;
8418             p++;
8419         }
8420         GameEnds(moveType, p, GE_FILE);
8421         done = TRUE;
8422         if (cmailMsgLoaded) {
8423             ClearHighlights();
8424             flipView = WhiteOnMove(currentMove);
8425             if (moveType == GameUnfinished) flipView = !flipView;
8426             if (appData.debugMode)
8427               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8428         }
8429         break;
8430
8431       case (ChessMove) 0:       /* end of file */
8432         if (appData.debugMode)
8433           fprintf(debugFP, "Parser hit end of file\n");
8434         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8435                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8436           case MT_NONE:
8437           case MT_CHECK:
8438             break;
8439           case MT_CHECKMATE:
8440           case MT_STAINMATE:
8441             if (WhiteOnMove(currentMove)) {
8442                 GameEnds(BlackWins, "Black mates", GE_FILE);
8443             } else {
8444                 GameEnds(WhiteWins, "White mates", GE_FILE);
8445             }
8446             break;
8447           case MT_STALEMATE:
8448             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8449             break;
8450         }
8451         done = TRUE;
8452         break;
8453
8454       case MoveNumberOne:
8455         if (lastLoadGameStart == GNUChessGame) {
8456             /* GNUChessGames have numbers, but they aren't move numbers */
8457             if (appData.debugMode)
8458               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8459                       yy_text, (int) moveType);
8460             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8461         }
8462         /* else fall thru */
8463
8464       case XBoardGame:
8465       case GNUChessGame:
8466       case PGNTag:
8467         /* Reached start of next game in file */
8468         if (appData.debugMode)
8469           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8470         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8471                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8472           case MT_NONE:
8473           case MT_CHECK:
8474             break;
8475           case MT_CHECKMATE:
8476           case MT_STAINMATE:
8477             if (WhiteOnMove(currentMove)) {
8478                 GameEnds(BlackWins, "Black mates", GE_FILE);
8479             } else {
8480                 GameEnds(WhiteWins, "White mates", GE_FILE);
8481             }
8482             break;
8483           case MT_STALEMATE:
8484             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8485             break;
8486         }
8487         done = TRUE;
8488         break;
8489
8490       case PositionDiagram:     /* should not happen; ignore */
8491       case ElapsedTime:         /* ignore */
8492       case NAG:                 /* ignore */
8493         if (appData.debugMode)
8494           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8495                   yy_text, (int) moveType);
8496         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8497
8498       case IllegalMove:
8499         if (appData.testLegality) {
8500             if (appData.debugMode)
8501               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8502             sprintf(move, _("Illegal move: %d.%s%s"),
8503                     (forwardMostMove / 2) + 1,
8504                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8505             DisplayError(move, 0);
8506             done = TRUE;
8507         } else {
8508             if (appData.debugMode)
8509               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8510                       yy_text, currentMoveString);
8511             fromX = currentMoveString[0] - AAA;
8512             fromY = currentMoveString[1] - ONE;
8513             toX = currentMoveString[2] - AAA;
8514             toY = currentMoveString[3] - ONE;
8515             promoChar = currentMoveString[4];
8516         }
8517         break;
8518
8519       case AmbiguousMove:
8520         if (appData.debugMode)
8521           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8522         sprintf(move, _("Ambiguous move: %d.%s%s"),
8523                 (forwardMostMove / 2) + 1,
8524                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8525         DisplayError(move, 0);
8526         done = TRUE;
8527         break;
8528
8529       default:
8530       case ImpossibleMove:
8531         if (appData.debugMode)
8532           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8533         sprintf(move, _("Illegal move: %d.%s%s"),
8534                 (forwardMostMove / 2) + 1,
8535                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8536         DisplayError(move, 0);
8537         done = TRUE;
8538         break;
8539     }
8540
8541     if (done) {
8542         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8543             DrawPosition(FALSE, boards[currentMove]);
8544             DisplayBothClocks();
8545             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8546               DisplayComment(currentMove - 1, commentList[currentMove]);
8547         }
8548         (void) StopLoadGameTimer();
8549         gameFileFP = NULL;
8550         cmailOldMove = forwardMostMove;
8551         return FALSE;
8552     } else {
8553         /* currentMoveString is set as a side-effect of yylex */
8554         strcat(currentMoveString, "\n");
8555         strcpy(moveList[forwardMostMove], currentMoveString);
8556         
8557         thinkOutput[0] = NULLCHAR;
8558         MakeMove(fromX, fromY, toX, toY, promoChar);
8559         currentMove = forwardMostMove;
8560         return TRUE;
8561     }
8562 }
8563
8564 /* Load the nth game from the given file */
8565 int
8566 LoadGameFromFile(filename, n, title, useList)
8567      char *filename;
8568      int n;
8569      char *title;
8570      /*Boolean*/ int useList;
8571 {
8572     FILE *f;
8573     char buf[MSG_SIZ];
8574
8575     if (strcmp(filename, "-") == 0) {
8576         f = stdin;
8577         title = "stdin";
8578     } else {
8579         f = fopen(filename, "rb");
8580         if (f == NULL) {
8581           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8582             DisplayError(buf, errno);
8583             return FALSE;
8584         }
8585     }
8586     if (fseek(f, 0, 0) == -1) {
8587         /* f is not seekable; probably a pipe */
8588         useList = FALSE;
8589     }
8590     if (useList && n == 0) {
8591         int error = GameListBuild(f);
8592         if (error) {
8593             DisplayError(_("Cannot build game list"), error);
8594         } else if (!ListEmpty(&gameList) &&
8595                    ((ListGame *) gameList.tailPred)->number > 1) {
8596             GameListPopUp(f, title);
8597             return TRUE;
8598         }
8599         GameListDestroy();
8600         n = 1;
8601     }
8602     if (n == 0) n = 1;
8603     return LoadGame(f, n, title, FALSE);
8604 }
8605
8606
8607 void
8608 MakeRegisteredMove()
8609 {
8610     int fromX, fromY, toX, toY;
8611     char promoChar;
8612     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8613         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8614           case CMAIL_MOVE:
8615           case CMAIL_DRAW:
8616             if (appData.debugMode)
8617               fprintf(debugFP, "Restoring %s for game %d\n",
8618                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8619     
8620             thinkOutput[0] = NULLCHAR;
8621             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8622             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8623             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8624             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8625             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8626             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8627             MakeMove(fromX, fromY, toX, toY, promoChar);
8628             ShowMove(fromX, fromY, toX, toY);
8629               
8630             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8631                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8632               case MT_NONE:
8633               case MT_CHECK:
8634                 break;
8635                 
8636               case MT_CHECKMATE:
8637               case MT_STAINMATE:
8638                 if (WhiteOnMove(currentMove)) {
8639                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8640                 } else {
8641                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8642                 }
8643                 break;
8644                 
8645               case MT_STALEMATE:
8646                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8647                 break;
8648             }
8649
8650             break;
8651             
8652           case CMAIL_RESIGN:
8653             if (WhiteOnMove(currentMove)) {
8654                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8655             } else {
8656                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8657             }
8658             break;
8659             
8660           case CMAIL_ACCEPT:
8661             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8662             break;
8663               
8664           default:
8665             break;
8666         }
8667     }
8668
8669     return;
8670 }
8671
8672 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8673 int
8674 CmailLoadGame(f, gameNumber, title, useList)
8675      FILE *f;
8676      int gameNumber;
8677      char *title;
8678      int useList;
8679 {
8680     int retVal;
8681
8682     if (gameNumber > nCmailGames) {
8683         DisplayError(_("No more games in this message"), 0);
8684         return FALSE;
8685     }
8686     if (f == lastLoadGameFP) {
8687         int offset = gameNumber - lastLoadGameNumber;
8688         if (offset == 0) {
8689             cmailMsg[0] = NULLCHAR;
8690             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8691                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8692                 nCmailMovesRegistered--;
8693             }
8694             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8695             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8696                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8697             }
8698         } else {
8699             if (! RegisterMove()) return FALSE;
8700         }
8701     }
8702
8703     retVal = LoadGame(f, gameNumber, title, useList);
8704
8705     /* Make move registered during previous look at this game, if any */
8706     MakeRegisteredMove();
8707
8708     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8709         commentList[currentMove]
8710           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8711         DisplayComment(currentMove - 1, commentList[currentMove]);
8712     }
8713
8714     return retVal;
8715 }
8716
8717 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8718 int
8719 ReloadGame(offset)
8720      int offset;
8721 {
8722     int gameNumber = lastLoadGameNumber + offset;
8723     if (lastLoadGameFP == NULL) {
8724         DisplayError(_("No game has been loaded yet"), 0);
8725         return FALSE;
8726     }
8727     if (gameNumber <= 0) {
8728         DisplayError(_("Can't back up any further"), 0);
8729         return FALSE;
8730     }
8731     if (cmailMsgLoaded) {
8732         return CmailLoadGame(lastLoadGameFP, gameNumber,
8733                              lastLoadGameTitle, lastLoadGameUseList);
8734     } else {
8735         return LoadGame(lastLoadGameFP, gameNumber,
8736                         lastLoadGameTitle, lastLoadGameUseList);
8737     }
8738 }
8739
8740
8741
8742 /* Load the nth game from open file f */
8743 int
8744 LoadGame(f, gameNumber, title, useList)
8745      FILE *f;
8746      int gameNumber;
8747      char *title;
8748      int useList;
8749 {
8750     ChessMove cm;
8751     char buf[MSG_SIZ];
8752     int gn = gameNumber;
8753     ListGame *lg = NULL;
8754     int numPGNTags = 0;
8755     int err;
8756     GameMode oldGameMode;
8757     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8758
8759     if (appData.debugMode) 
8760         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8761
8762     if (gameMode == Training )
8763         SetTrainingModeOff();
8764
8765     oldGameMode = gameMode;
8766     if (gameMode != BeginningOfGame) {
8767       Reset(FALSE, TRUE);
8768     }
8769
8770     gameFileFP = f;
8771     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8772         fclose(lastLoadGameFP);
8773     }
8774
8775     if (useList) {
8776         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8777         
8778         if (lg) {
8779             fseek(f, lg->offset, 0);
8780             GameListHighlight(gameNumber);
8781             gn = 1;
8782         }
8783         else {
8784             DisplayError(_("Game number out of range"), 0);
8785             return FALSE;
8786         }
8787     } else {
8788         GameListDestroy();
8789         if (fseek(f, 0, 0) == -1) {
8790             if (f == lastLoadGameFP ?
8791                 gameNumber == lastLoadGameNumber + 1 :
8792                 gameNumber == 1) {
8793                 gn = 1;
8794             } else {
8795                 DisplayError(_("Can't seek on game file"), 0);
8796                 return FALSE;
8797             }
8798         }
8799     }
8800     lastLoadGameFP = f;
8801     lastLoadGameNumber = gameNumber;
8802     strcpy(lastLoadGameTitle, title);
8803     lastLoadGameUseList = useList;
8804
8805     yynewfile(f);
8806
8807     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8808       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8809                 lg->gameInfo.black);
8810             DisplayTitle(buf);
8811     } else if (*title != NULLCHAR) {
8812         if (gameNumber > 1) {
8813             sprintf(buf, "%s %d", title, gameNumber);
8814             DisplayTitle(buf);
8815         } else {
8816             DisplayTitle(title);
8817         }
8818     }
8819
8820     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8821         gameMode = PlayFromGameFile;
8822         ModeHighlight();
8823     }
8824
8825     currentMove = forwardMostMove = backwardMostMove = 0;
8826     CopyBoard(boards[0], initialPosition);
8827     StopClocks();
8828
8829     /*
8830      * Skip the first gn-1 games in the file.
8831      * Also skip over anything that precedes an identifiable 
8832      * start of game marker, to avoid being confused by 
8833      * garbage at the start of the file.  Currently 
8834      * recognized start of game markers are the move number "1",
8835      * the pattern "gnuchess .* game", the pattern
8836      * "^[#;%] [^ ]* game file", and a PGN tag block.  
8837      * A game that starts with one of the latter two patterns
8838      * will also have a move number 1, possibly
8839      * following a position diagram.
8840      * 5-4-02: Let's try being more lenient and allowing a game to
8841      * start with an unnumbered move.  Does that break anything?
8842      */
8843     cm = lastLoadGameStart = (ChessMove) 0;
8844     while (gn > 0) {
8845         yyboardindex = forwardMostMove;
8846         cm = (ChessMove) yylex();
8847         switch (cm) {
8848           case (ChessMove) 0:
8849             if (cmailMsgLoaded) {
8850                 nCmailGames = CMAIL_MAX_GAMES - gn;
8851             } else {
8852                 Reset(TRUE, TRUE);
8853                 DisplayError(_("Game not found in file"), 0);
8854             }
8855             return FALSE;
8856
8857           case GNUChessGame:
8858           case XBoardGame:
8859             gn--;
8860             lastLoadGameStart = cm;
8861             break;
8862             
8863           case MoveNumberOne:
8864             switch (lastLoadGameStart) {
8865               case GNUChessGame:
8866               case XBoardGame:
8867               case PGNTag:
8868                 break;
8869               case MoveNumberOne:
8870               case (ChessMove) 0:
8871                 gn--;           /* count this game */
8872                 lastLoadGameStart = cm;
8873                 break;
8874               default:
8875                 /* impossible */
8876                 break;
8877             }
8878             break;
8879
8880           case PGNTag:
8881             switch (lastLoadGameStart) {
8882               case GNUChessGame:
8883               case PGNTag:
8884               case MoveNumberOne:
8885               case (ChessMove) 0:
8886                 gn--;           /* count this game */
8887                 lastLoadGameStart = cm;
8888                 break;
8889               case XBoardGame:
8890                 lastLoadGameStart = cm; /* game counted already */
8891                 break;
8892               default:
8893                 /* impossible */
8894                 break;
8895             }
8896             if (gn > 0) {
8897                 do {
8898                     yyboardindex = forwardMostMove;
8899                     cm = (ChessMove) yylex();
8900                 } while (cm == PGNTag || cm == Comment);
8901             }
8902             break;
8903
8904           case WhiteWins:
8905           case BlackWins:
8906           case GameIsDrawn:
8907             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8908                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
8909                     != CMAIL_OLD_RESULT) {
8910                     nCmailResults ++ ;
8911                     cmailResult[  CMAIL_MAX_GAMES
8912                                 - gn - 1] = CMAIL_OLD_RESULT;
8913                 }
8914             }
8915             break;
8916
8917           case NormalMove:
8918             /* Only a NormalMove can be at the start of a game
8919              * without a position diagram. */
8920             if (lastLoadGameStart == (ChessMove) 0) {
8921               gn--;
8922               lastLoadGameStart = MoveNumberOne;
8923             }
8924             break;
8925
8926           default:
8927             break;
8928         }
8929     }
8930     
8931     if (appData.debugMode)
8932       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8933
8934     if (cm == XBoardGame) {
8935         /* Skip any header junk before position diagram and/or move 1 */
8936         for (;;) {
8937             yyboardindex = forwardMostMove;
8938             cm = (ChessMove) yylex();
8939
8940             if (cm == (ChessMove) 0 ||
8941                 cm == GNUChessGame || cm == XBoardGame) {
8942                 /* Empty game; pretend end-of-file and handle later */
8943                 cm = (ChessMove) 0;
8944                 break;
8945             }
8946
8947             if (cm == MoveNumberOne || cm == PositionDiagram ||
8948                 cm == PGNTag || cm == Comment)
8949               break;
8950         }
8951     } else if (cm == GNUChessGame) {
8952         if (gameInfo.event != NULL) {
8953             free(gameInfo.event);
8954         }
8955         gameInfo.event = StrSave(yy_text);
8956     }   
8957
8958     startedFromSetupPosition = FALSE;
8959     while (cm == PGNTag) {
8960         if (appData.debugMode) 
8961           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
8962         err = ParsePGNTag(yy_text, &gameInfo);
8963         if (!err) numPGNTags++;
8964
8965         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
8966         if(gameInfo.variant != oldVariant) {
8967             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
8968             InitPosition(TRUE);
8969             oldVariant = gameInfo.variant;
8970             if (appData.debugMode) 
8971               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
8972         }
8973
8974
8975         if (gameInfo.fen != NULL) {
8976           Board initial_position;
8977           startedFromSetupPosition = TRUE;
8978           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
8979             Reset(TRUE, TRUE);
8980             DisplayError(_("Bad FEN position in file"), 0);
8981             return FALSE;
8982           }
8983           CopyBoard(boards[0], initial_position);
8984           if (blackPlaysFirst) {
8985             currentMove = forwardMostMove = backwardMostMove = 1;
8986             CopyBoard(boards[1], initial_position);
8987             strcpy(moveList[0], "");
8988             strcpy(parseList[0], "");
8989             timeRemaining[0][1] = whiteTimeRemaining;
8990             timeRemaining[1][1] = blackTimeRemaining;
8991             if (commentList[0] != NULL) {
8992               commentList[1] = commentList[0];
8993               commentList[0] = NULL;
8994             }
8995           } else {
8996             currentMove = forwardMostMove = backwardMostMove = 0;
8997           }
8998           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
8999           {   int i;
9000               initialRulePlies = FENrulePlies;
9001               epStatus[forwardMostMove] = FENepStatus;
9002               for( i=0; i< nrCastlingRights; i++ )
9003                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9004           }
9005           yyboardindex = forwardMostMove;
9006           free(gameInfo.fen);
9007           gameInfo.fen = NULL;
9008         }
9009
9010         yyboardindex = forwardMostMove;
9011         cm = (ChessMove) yylex();
9012
9013         /* Handle comments interspersed among the tags */
9014         while (cm == Comment) {
9015             char *p;
9016             if (appData.debugMode) 
9017               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9018             p = yy_text;
9019             if (*p == '{' || *p == '[' || *p == '(') {
9020                 p[strlen(p) - 1] = NULLCHAR;
9021                 p++;
9022             }
9023             while (*p == '\n') p++;
9024             AppendComment(currentMove, p);
9025             yyboardindex = forwardMostMove;
9026             cm = (ChessMove) yylex();
9027         }
9028     }
9029
9030     /* don't rely on existence of Event tag since if game was
9031      * pasted from clipboard the Event tag may not exist
9032      */
9033     if (numPGNTags > 0){
9034         char *tags;
9035         if (gameInfo.variant == VariantNormal) {
9036           gameInfo.variant = StringToVariant(gameInfo.event);
9037         }
9038         if (!matchMode) {
9039           if( appData.autoDisplayTags ) {
9040             tags = PGNTags(&gameInfo);
9041             TagsPopUp(tags, CmailMsg());
9042             free(tags);
9043           }
9044         }
9045     } else {
9046         /* Make something up, but don't display it now */
9047         SetGameInfo();
9048         TagsPopDown();
9049     }
9050
9051     if (cm == PositionDiagram) {
9052         int i, j;
9053         char *p;
9054         Board initial_position;
9055
9056         if (appData.debugMode)
9057           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9058
9059         if (!startedFromSetupPosition) {
9060             p = yy_text;
9061             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9062               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9063                 switch (*p) {
9064                   case '[':
9065                   case '-':
9066                   case ' ':
9067                   case '\t':
9068                   case '\n':
9069                   case '\r':
9070                     break;
9071                   default:
9072                     initial_position[i][j++] = CharToPiece(*p);
9073                     break;
9074                 }
9075             while (*p == ' ' || *p == '\t' ||
9076                    *p == '\n' || *p == '\r') p++;
9077         
9078             if (strncmp(p, "black", strlen("black"))==0)
9079               blackPlaysFirst = TRUE;
9080             else
9081               blackPlaysFirst = FALSE;
9082             startedFromSetupPosition = TRUE;
9083         
9084             CopyBoard(boards[0], initial_position);
9085             if (blackPlaysFirst) {
9086                 currentMove = forwardMostMove = backwardMostMove = 1;
9087                 CopyBoard(boards[1], initial_position);
9088                 strcpy(moveList[0], "");
9089                 strcpy(parseList[0], "");
9090                 timeRemaining[0][1] = whiteTimeRemaining;
9091                 timeRemaining[1][1] = blackTimeRemaining;
9092                 if (commentList[0] != NULL) {
9093                     commentList[1] = commentList[0];
9094                     commentList[0] = NULL;
9095                 }
9096             } else {
9097                 currentMove = forwardMostMove = backwardMostMove = 0;
9098             }
9099         }
9100         yyboardindex = forwardMostMove;
9101         cm = (ChessMove) yylex();
9102     }
9103
9104     if (first.pr == NoProc) {
9105         StartChessProgram(&first);
9106     }
9107     InitChessProgram(&first, FALSE);
9108     SendToProgram("force\n", &first);
9109     if (startedFromSetupPosition) {
9110         SendBoard(&first, forwardMostMove);
9111     if (appData.debugMode) {
9112         fprintf(debugFP, "Load Game\n");
9113     }
9114         DisplayBothClocks();
9115     }      
9116
9117     /* [HGM] server: flag to write setup moves in broadcast file as one */
9118     loadFlag = appData.suppressLoadMoves;
9119
9120     while (cm == Comment) {
9121         char *p;
9122         if (appData.debugMode) 
9123           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9124         p = yy_text;
9125         if (*p == '{' || *p == '[' || *p == '(') {
9126             p[strlen(p) - 1] = NULLCHAR;
9127             p++;
9128         }
9129         while (*p == '\n') p++;
9130         AppendComment(currentMove, p);
9131         yyboardindex = forwardMostMove;
9132         cm = (ChessMove) yylex();
9133     }
9134
9135     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9136         cm == WhiteWins || cm == BlackWins ||
9137         cm == GameIsDrawn || cm == GameUnfinished) {
9138         DisplayMessage("", _("No moves in game"));
9139         if (cmailMsgLoaded) {
9140             if (appData.debugMode)
9141               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9142             ClearHighlights();
9143             flipView = FALSE;
9144         }
9145         DrawPosition(FALSE, boards[currentMove]);
9146         DisplayBothClocks();
9147         gameMode = EditGame;
9148         ModeHighlight();
9149         gameFileFP = NULL;
9150         cmailOldMove = 0;
9151         return TRUE;
9152     }
9153
9154     // [HGM] PV info: routine tests if comment empty
9155     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9156         DisplayComment(currentMove - 1, commentList[currentMove]);
9157     }
9158     if (!matchMode && appData.timeDelay != 0) 
9159       DrawPosition(FALSE, boards[currentMove]);
9160
9161     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9162       programStats.ok_to_send = 1;
9163     }
9164
9165     /* if the first token after the PGN tags is a move
9166      * and not move number 1, retrieve it from the parser 
9167      */
9168     if (cm != MoveNumberOne)
9169         LoadGameOneMove(cm);
9170
9171     /* load the remaining moves from the file */
9172     while (LoadGameOneMove((ChessMove)0)) {
9173       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9174       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9175     }
9176
9177     /* rewind to the start of the game */
9178     currentMove = backwardMostMove;
9179
9180     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9181
9182     if (oldGameMode == AnalyzeFile ||
9183         oldGameMode == AnalyzeMode) {
9184       AnalyzeFileEvent();
9185     }
9186
9187     if (matchMode || appData.timeDelay == 0) {
9188       ToEndEvent();
9189       gameMode = EditGame;
9190       ModeHighlight();
9191     } else if (appData.timeDelay > 0) {
9192       AutoPlayGameLoop();
9193     }
9194
9195     if (appData.debugMode) 
9196         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9197
9198     loadFlag = 0; /* [HGM] true game starts */
9199     return TRUE;
9200 }
9201
9202 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9203 int
9204 ReloadPosition(offset)
9205      int offset;
9206 {
9207     int positionNumber = lastLoadPositionNumber + offset;
9208     if (lastLoadPositionFP == NULL) {
9209         DisplayError(_("No position has been loaded yet"), 0);
9210         return FALSE;
9211     }
9212     if (positionNumber <= 0) {
9213         DisplayError(_("Can't back up any further"), 0);
9214         return FALSE;
9215     }
9216     return LoadPosition(lastLoadPositionFP, positionNumber,
9217                         lastLoadPositionTitle);
9218 }
9219
9220 /* Load the nth position from the given file */
9221 int
9222 LoadPositionFromFile(filename, n, title)
9223      char *filename;
9224      int n;
9225      char *title;
9226 {
9227     FILE *f;
9228     char buf[MSG_SIZ];
9229
9230     if (strcmp(filename, "-") == 0) {
9231         return LoadPosition(stdin, n, "stdin");
9232     } else {
9233         f = fopen(filename, "rb");
9234         if (f == NULL) {
9235             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9236             DisplayError(buf, errno);
9237             return FALSE;
9238         } else {
9239             return LoadPosition(f, n, title);
9240         }
9241     }
9242 }
9243
9244 /* Load the nth position from the given open file, and close it */
9245 int
9246 LoadPosition(f, positionNumber, title)
9247      FILE *f;
9248      int positionNumber;
9249      char *title;
9250 {
9251     char *p, line[MSG_SIZ];
9252     Board initial_position;
9253     int i, j, fenMode, pn;
9254     
9255     if (gameMode == Training )
9256         SetTrainingModeOff();
9257
9258     if (gameMode != BeginningOfGame) {
9259         Reset(FALSE, TRUE);
9260     }
9261     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9262         fclose(lastLoadPositionFP);
9263     }
9264     if (positionNumber == 0) positionNumber = 1;
9265     lastLoadPositionFP = f;
9266     lastLoadPositionNumber = positionNumber;
9267     strcpy(lastLoadPositionTitle, title);
9268     if (first.pr == NoProc) {
9269       StartChessProgram(&first);
9270       InitChessProgram(&first, FALSE);
9271     }    
9272     pn = positionNumber;
9273     if (positionNumber < 0) {
9274         /* Negative position number means to seek to that byte offset */
9275         if (fseek(f, -positionNumber, 0) == -1) {
9276             DisplayError(_("Can't seek on position file"), 0);
9277             return FALSE;
9278         };
9279         pn = 1;
9280     } else {
9281         if (fseek(f, 0, 0) == -1) {
9282             if (f == lastLoadPositionFP ?
9283                 positionNumber == lastLoadPositionNumber + 1 :
9284                 positionNumber == 1) {
9285                 pn = 1;
9286             } else {
9287                 DisplayError(_("Can't seek on position file"), 0);
9288                 return FALSE;
9289             }
9290         }
9291     }
9292     /* See if this file is FEN or old-style xboard */
9293     if (fgets(line, MSG_SIZ, f) == NULL) {
9294         DisplayError(_("Position not found in file"), 0);
9295         return FALSE;
9296     }
9297 #if 0
9298     switch (line[0]) {
9299       case '#':  case 'x':
9300       default:
9301         fenMode = FALSE;
9302         break;
9303       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':
9304       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':
9305       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':
9306       case '7':  case '8':  case '9':
9307       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':
9308       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':
9309       case 'C':  case 'W':             case 'c':  case 'w': 
9310         fenMode = TRUE;
9311         break;
9312     }
9313 #else
9314     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9315     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9316 #endif
9317
9318     if (pn >= 2) {
9319         if (fenMode || line[0] == '#') pn--;
9320         while (pn > 0) {
9321             /* skip positions before number pn */
9322             if (fgets(line, MSG_SIZ, f) == NULL) {
9323                 Reset(TRUE, TRUE);
9324                 DisplayError(_("Position not found in file"), 0);
9325                 return FALSE;
9326             }
9327             if (fenMode || line[0] == '#') pn--;
9328         }
9329     }
9330
9331     if (fenMode) {
9332         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9333             DisplayError(_("Bad FEN position in file"), 0);
9334             return FALSE;
9335         }
9336     } else {
9337         (void) fgets(line, MSG_SIZ, f);
9338         (void) fgets(line, MSG_SIZ, f);
9339     
9340         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9341             (void) fgets(line, MSG_SIZ, f);
9342             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9343                 if (*p == ' ')
9344                   continue;
9345                 initial_position[i][j++] = CharToPiece(*p);
9346             }
9347         }
9348     
9349         blackPlaysFirst = FALSE;
9350         if (!feof(f)) {
9351             (void) fgets(line, MSG_SIZ, f);
9352             if (strncmp(line, "black", strlen("black"))==0)
9353               blackPlaysFirst = TRUE;
9354         }
9355     }
9356     startedFromSetupPosition = TRUE;
9357     
9358     SendToProgram("force\n", &first);
9359     CopyBoard(boards[0], initial_position);
9360     if (blackPlaysFirst) {
9361         currentMove = forwardMostMove = backwardMostMove = 1;
9362         strcpy(moveList[0], "");
9363         strcpy(parseList[0], "");
9364         CopyBoard(boards[1], initial_position);
9365         DisplayMessage("", _("Black to play"));
9366     } else {
9367         currentMove = forwardMostMove = backwardMostMove = 0;
9368         DisplayMessage("", _("White to play"));
9369     }
9370           /* [HGM] copy FEN attributes as well */
9371           {   int i;
9372               initialRulePlies = FENrulePlies;
9373               epStatus[forwardMostMove] = FENepStatus;
9374               for( i=0; i< nrCastlingRights; i++ )
9375                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9376           }
9377     SendBoard(&first, forwardMostMove);
9378     if (appData.debugMode) {
9379 int i, j;
9380   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9381   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9382         fprintf(debugFP, "Load Position\n");
9383     }
9384
9385     if (positionNumber > 1) {
9386         sprintf(line, "%s %d", title, positionNumber);
9387         DisplayTitle(line);
9388     } else {
9389         DisplayTitle(title);
9390     }
9391     gameMode = EditGame;
9392     ModeHighlight();
9393     ResetClocks();
9394     timeRemaining[0][1] = whiteTimeRemaining;
9395     timeRemaining[1][1] = blackTimeRemaining;
9396     DrawPosition(FALSE, boards[currentMove]);
9397    
9398     return TRUE;
9399 }
9400
9401
9402 void
9403 CopyPlayerNameIntoFileName(dest, src)
9404      char **dest, *src;
9405 {
9406     while (*src != NULLCHAR && *src != ',') {
9407         if (*src == ' ') {
9408             *(*dest)++ = '_';
9409             src++;
9410         } else {
9411             *(*dest)++ = *src++;
9412         }
9413     }
9414 }
9415
9416 char *DefaultFileName(ext)
9417      char *ext;
9418 {
9419     static char def[MSG_SIZ];
9420     char *p;
9421
9422     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9423         p = def;
9424         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9425         *p++ = '-';
9426         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9427         *p++ = '.';
9428         strcpy(p, ext);
9429     } else {
9430         def[0] = NULLCHAR;
9431     }
9432     return def;
9433 }
9434
9435 /* Save the current game to the given file */
9436 int
9437 SaveGameToFile(filename, append)
9438      char *filename;
9439      int append;
9440 {
9441     FILE *f;
9442     char buf[MSG_SIZ];
9443
9444     if (strcmp(filename, "-") == 0) {
9445         return SaveGame(stdout, 0, NULL);
9446     } else {
9447         f = fopen(filename, append ? "a" : "w");
9448         if (f == NULL) {
9449             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9450             DisplayError(buf, errno);
9451             return FALSE;
9452         } else {
9453             return SaveGame(f, 0, NULL);
9454         }
9455     }
9456 }
9457
9458 char *
9459 SavePart(str)
9460      char *str;
9461 {
9462     static char buf[MSG_SIZ];
9463     char *p;
9464     
9465     p = strchr(str, ' ');
9466     if (p == NULL) return str;
9467     strncpy(buf, str, p - str);
9468     buf[p - str] = NULLCHAR;
9469     return buf;
9470 }
9471
9472 #define PGN_MAX_LINE 75
9473
9474 #define PGN_SIDE_WHITE  0
9475 #define PGN_SIDE_BLACK  1
9476
9477 /* [AS] */
9478 static int FindFirstMoveOutOfBook( int side )
9479 {
9480     int result = -1;
9481
9482     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9483         int index = backwardMostMove;
9484         int has_book_hit = 0;
9485
9486         if( (index % 2) != side ) {
9487             index++;
9488         }
9489
9490         while( index < forwardMostMove ) {
9491             /* Check to see if engine is in book */
9492             int depth = pvInfoList[index].depth;
9493             int score = pvInfoList[index].score;
9494             int in_book = 0;
9495
9496             if( depth <= 2 ) {
9497                 in_book = 1;
9498             }
9499             else if( score == 0 && depth == 63 ) {
9500                 in_book = 1; /* Zappa */
9501             }
9502             else if( score == 2 && depth == 99 ) {
9503                 in_book = 1; /* Abrok */
9504             }
9505
9506             has_book_hit += in_book;
9507
9508             if( ! in_book ) {
9509                 result = index;
9510
9511                 break;
9512             }
9513
9514             index += 2;
9515         }
9516     }
9517
9518     return result;
9519 }
9520
9521 /* [AS] */
9522 void GetOutOfBookInfo( char * buf )
9523 {
9524     int oob[2];
9525     int i;
9526     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9527
9528     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9529     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9530
9531     *buf = '\0';
9532
9533     if( oob[0] >= 0 || oob[1] >= 0 ) {
9534         for( i=0; i<2; i++ ) {
9535             int idx = oob[i];
9536
9537             if( idx >= 0 ) {
9538                 if( i > 0 && oob[0] >= 0 ) {
9539                     strcat( buf, "   " );
9540                 }
9541
9542                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9543                 sprintf( buf+strlen(buf), "%s%.2f", 
9544                     pvInfoList[idx].score >= 0 ? "+" : "",
9545                     pvInfoList[idx].score / 100.0 );
9546             }
9547         }
9548     }
9549 }
9550
9551 /* Save game in PGN style and close the file */
9552 int
9553 SaveGamePGN(f)
9554      FILE *f;
9555 {
9556     int i, offset, linelen, newblock;
9557     time_t tm;
9558 //    char *movetext;
9559     char numtext[32];
9560     int movelen, numlen, blank;
9561     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9562
9563     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9564     
9565     tm = time((time_t *) NULL);
9566     
9567     PrintPGNTags(f, &gameInfo);
9568     
9569     if (backwardMostMove > 0 || startedFromSetupPosition) {
9570         char *fen = PositionToFEN(backwardMostMove, NULL);
9571         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9572         fprintf(f, "\n{--------------\n");
9573         PrintPosition(f, backwardMostMove);
9574         fprintf(f, "--------------}\n");
9575         free(fen);
9576     }
9577     else {
9578         /* [AS] Out of book annotation */
9579         if( appData.saveOutOfBookInfo ) {
9580             char buf[64];
9581
9582             GetOutOfBookInfo( buf );
9583
9584             if( buf[0] != '\0' ) {
9585                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9586             }
9587         }
9588
9589         fprintf(f, "\n");
9590     }
9591
9592     i = backwardMostMove;
9593     linelen = 0;
9594     newblock = TRUE;
9595
9596     while (i < forwardMostMove) {
9597         /* Print comments preceding this move */
9598         if (commentList[i] != NULL) {
9599             if (linelen > 0) fprintf(f, "\n");
9600             fprintf(f, "{\n%s}\n", commentList[i]);
9601             linelen = 0;
9602             newblock = TRUE;
9603         }
9604
9605         /* Format move number */
9606         if ((i % 2) == 0) {
9607             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9608         } else {
9609             if (newblock) {
9610                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9611             } else {
9612                 numtext[0] = NULLCHAR;
9613             }
9614         }
9615         numlen = strlen(numtext);
9616         newblock = FALSE;
9617
9618         /* Print move number */
9619         blank = linelen > 0 && numlen > 0;
9620         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9621             fprintf(f, "\n");
9622             linelen = 0;
9623             blank = 0;
9624         }
9625         if (blank) {
9626             fprintf(f, " ");
9627             linelen++;
9628         }
9629         fprintf(f, numtext);
9630         linelen += numlen;
9631
9632         /* Get move */
9633         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9634         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9635 #if 0
9636         // SavePart already does this!
9637         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9638                 int p = movelen - 1;
9639                 if(move_buffer[p] == ' ') p--;
9640                 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
9641                     while(p && move_buffer[--p] != '(');
9642                     if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
9643                 }
9644         }
9645 #endif
9646         /* Print move */
9647         blank = linelen > 0 && movelen > 0;
9648         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9649             fprintf(f, "\n");
9650             linelen = 0;
9651             blank = 0;
9652         }
9653         if (blank) {
9654             fprintf(f, " ");
9655             linelen++;
9656         }
9657         fprintf(f, move_buffer);
9658         linelen += movelen;
9659
9660         /* [AS] Add PV info if present */
9661         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9662             /* [HGM] add time */
9663             char buf[MSG_SIZ]; int seconds = 0;
9664
9665 #if 1
9666             if(i >= backwardMostMove) {
9667                 if(WhiteOnMove(i))
9668                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9669                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9670                 else
9671                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9672                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9673             }
9674             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9675 #else
9676             seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
9677 #endif
9678
9679             if( seconds <= 0) buf[0] = 0; else
9680             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9681                 seconds = (seconds + 4)/10; // round to full seconds
9682                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9683                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9684             }
9685
9686             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9687                 pvInfoList[i].score >= 0 ? "+" : "",
9688                 pvInfoList[i].score / 100.0,
9689                 pvInfoList[i].depth,
9690                 buf );
9691
9692             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9693
9694             /* Print score/depth */
9695             blank = linelen > 0 && movelen > 0;
9696             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9697                 fprintf(f, "\n");
9698                 linelen = 0;
9699                 blank = 0;
9700             }
9701             if (blank) {
9702                 fprintf(f, " ");
9703                 linelen++;
9704             }
9705             fprintf(f, move_buffer);
9706             linelen += movelen;
9707         }
9708
9709         i++;
9710     }
9711     
9712     /* Start a new line */
9713     if (linelen > 0) fprintf(f, "\n");
9714
9715     /* Print comments after last move */
9716     if (commentList[i] != NULL) {
9717         fprintf(f, "{\n%s}\n", commentList[i]);
9718     }
9719
9720     /* Print result */
9721     if (gameInfo.resultDetails != NULL &&
9722         gameInfo.resultDetails[0] != NULLCHAR) {
9723         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9724                 PGNResult(gameInfo.result));
9725     } else {
9726         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9727     }
9728
9729     fclose(f);
9730     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9731     return TRUE;
9732 }
9733
9734 /* Save game in old style and close the file */
9735 int
9736 SaveGameOldStyle(f)
9737      FILE *f;
9738 {
9739     int i, offset;
9740     time_t tm;
9741     
9742     tm = time((time_t *) NULL);
9743     
9744     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9745     PrintOpponents(f);
9746     
9747     if (backwardMostMove > 0 || startedFromSetupPosition) {
9748         fprintf(f, "\n[--------------\n");
9749         PrintPosition(f, backwardMostMove);
9750         fprintf(f, "--------------]\n");
9751     } else {
9752         fprintf(f, "\n");
9753     }
9754
9755     i = backwardMostMove;
9756     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9757
9758     while (i < forwardMostMove) {
9759         if (commentList[i] != NULL) {
9760             fprintf(f, "[%s]\n", commentList[i]);
9761         }
9762
9763         if ((i % 2) == 1) {
9764             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
9765             i++;
9766         } else {
9767             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
9768             i++;
9769             if (commentList[i] != NULL) {
9770                 fprintf(f, "\n");
9771                 continue;
9772             }
9773             if (i >= forwardMostMove) {
9774                 fprintf(f, "\n");
9775                 break;
9776             }
9777             fprintf(f, "%s\n", parseList[i]);
9778             i++;
9779         }
9780     }
9781     
9782     if (commentList[i] != NULL) {
9783         fprintf(f, "[%s]\n", commentList[i]);
9784     }
9785
9786     /* This isn't really the old style, but it's close enough */
9787     if (gameInfo.resultDetails != NULL &&
9788         gameInfo.resultDetails[0] != NULLCHAR) {
9789         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9790                 gameInfo.resultDetails);
9791     } else {
9792         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9793     }
9794
9795     fclose(f);
9796     return TRUE;
9797 }
9798
9799 /* Save the current game to open file f and close the file */
9800 int
9801 SaveGame(f, dummy, dummy2)
9802      FILE *f;
9803      int dummy;
9804      char *dummy2;
9805 {
9806     if (gameMode == EditPosition) EditPositionDone();
9807     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9808     if (appData.oldSaveStyle)
9809       return SaveGameOldStyle(f);
9810     else
9811       return SaveGamePGN(f);
9812 }
9813
9814 /* Save the current position to the given file */
9815 int
9816 SavePositionToFile(filename)
9817      char *filename;
9818 {
9819     FILE *f;
9820     char buf[MSG_SIZ];
9821
9822     if (strcmp(filename, "-") == 0) {
9823         return SavePosition(stdout, 0, NULL);
9824     } else {
9825         f = fopen(filename, "a");
9826         if (f == NULL) {
9827             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9828             DisplayError(buf, errno);
9829             return FALSE;
9830         } else {
9831             SavePosition(f, 0, NULL);
9832             return TRUE;
9833         }
9834     }
9835 }
9836
9837 /* Save the current position to the given open file and close the file */
9838 int
9839 SavePosition(f, dummy, dummy2)
9840      FILE *f;
9841      int dummy;
9842      char *dummy2;
9843 {
9844     time_t tm;
9845     char *fen;
9846     
9847     if (appData.oldSaveStyle) {
9848         tm = time((time_t *) NULL);
9849     
9850         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9851         PrintOpponents(f);
9852         fprintf(f, "[--------------\n");
9853         PrintPosition(f, currentMove);
9854         fprintf(f, "--------------]\n");
9855     } else {
9856         fen = PositionToFEN(currentMove, NULL);
9857         fprintf(f, "%s\n", fen);
9858         free(fen);
9859     }
9860     fclose(f);
9861     return TRUE;
9862 }
9863
9864 void
9865 ReloadCmailMsgEvent(unregister)
9866      int unregister;
9867 {
9868 #if !WIN32
9869     static char *inFilename = NULL;
9870     static char *outFilename;
9871     int i;
9872     struct stat inbuf, outbuf;
9873     int status;
9874     
9875     /* Any registered moves are unregistered if unregister is set, */
9876     /* i.e. invoked by the signal handler */
9877     if (unregister) {
9878         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9879             cmailMoveRegistered[i] = FALSE;
9880             if (cmailCommentList[i] != NULL) {
9881                 free(cmailCommentList[i]);
9882                 cmailCommentList[i] = NULL;
9883             }
9884         }
9885         nCmailMovesRegistered = 0;
9886     }
9887
9888     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9889         cmailResult[i] = CMAIL_NOT_RESULT;
9890     }
9891     nCmailResults = 0;
9892
9893     if (inFilename == NULL) {
9894         /* Because the filenames are static they only get malloced once  */
9895         /* and they never get freed                                      */
9896         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9897         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9898
9899         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9900         sprintf(outFilename, "%s.out", appData.cmailGameName);
9901     }
9902     
9903     status = stat(outFilename, &outbuf);
9904     if (status < 0) {
9905         cmailMailedMove = FALSE;
9906     } else {
9907         status = stat(inFilename, &inbuf);
9908         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9909     }
9910     
9911     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9912        counts the games, notes how each one terminated, etc.
9913        
9914        It would be nice to remove this kludge and instead gather all
9915        the information while building the game list.  (And to keep it
9916        in the game list nodes instead of having a bunch of fixed-size
9917        parallel arrays.)  Note this will require getting each game's
9918        termination from the PGN tags, as the game list builder does
9919        not process the game moves.  --mann
9920        */
9921     cmailMsgLoaded = TRUE;
9922     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9923     
9924     /* Load first game in the file or popup game menu */
9925     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9926
9927 #endif /* !WIN32 */
9928     return;
9929 }
9930
9931 int
9932 RegisterMove()
9933 {
9934     FILE *f;
9935     char string[MSG_SIZ];
9936
9937     if (   cmailMailedMove
9938         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9939         return TRUE;            /* Allow free viewing  */
9940     }
9941
9942     /* Unregister move to ensure that we don't leave RegisterMove        */
9943     /* with the move registered when the conditions for registering no   */
9944     /* longer hold                                                       */
9945     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9946         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9947         nCmailMovesRegistered --;
9948
9949         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
9950           {
9951               free(cmailCommentList[lastLoadGameNumber - 1]);
9952               cmailCommentList[lastLoadGameNumber - 1] = NULL;
9953           }
9954     }
9955
9956     if (cmailOldMove == -1) {
9957         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
9958         return FALSE;
9959     }
9960
9961     if (currentMove > cmailOldMove + 1) {
9962         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
9963         return FALSE;
9964     }
9965
9966     if (currentMove < cmailOldMove) {
9967         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
9968         return FALSE;
9969     }
9970
9971     if (forwardMostMove > currentMove) {
9972         /* Silently truncate extra moves */
9973         TruncateGame();
9974     }
9975
9976     if (   (currentMove == cmailOldMove + 1)
9977         || (   (currentMove == cmailOldMove)
9978             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
9979                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
9980         if (gameInfo.result != GameUnfinished) {
9981             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
9982         }
9983
9984         if (commentList[currentMove] != NULL) {
9985             cmailCommentList[lastLoadGameNumber - 1]
9986               = StrSave(commentList[currentMove]);
9987         }
9988         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
9989
9990         if (appData.debugMode)
9991           fprintf(debugFP, "Saving %s for game %d\n",
9992                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9993
9994         sprintf(string,
9995                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
9996         
9997         f = fopen(string, "w");
9998         if (appData.oldSaveStyle) {
9999             SaveGameOldStyle(f); /* also closes the file */
10000             
10001             sprintf(string, "%s.pos.out", appData.cmailGameName);
10002             f = fopen(string, "w");
10003             SavePosition(f, 0, NULL); /* also closes the file */
10004         } else {
10005             fprintf(f, "{--------------\n");
10006             PrintPosition(f, currentMove);
10007             fprintf(f, "--------------}\n\n");
10008             
10009             SaveGame(f, 0, NULL); /* also closes the file*/
10010         }
10011         
10012         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10013         nCmailMovesRegistered ++;
10014     } else if (nCmailGames == 1) {
10015         DisplayError(_("You have not made a move yet"), 0);
10016         return FALSE;
10017     }
10018
10019     return TRUE;
10020 }
10021
10022 void
10023 MailMoveEvent()
10024 {
10025 #if !WIN32
10026     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10027     FILE *commandOutput;
10028     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10029     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10030     int nBuffers;
10031     int i;
10032     int archived;
10033     char *arcDir;
10034
10035     if (! cmailMsgLoaded) {
10036         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10037         return;
10038     }
10039
10040     if (nCmailGames == nCmailResults) {
10041         DisplayError(_("No unfinished games"), 0);
10042         return;
10043     }
10044
10045 #if CMAIL_PROHIBIT_REMAIL
10046     if (cmailMailedMove) {
10047         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);
10048         DisplayError(msg, 0);
10049         return;
10050     }
10051 #endif
10052
10053     if (! (cmailMailedMove || RegisterMove())) return;
10054     
10055     if (   cmailMailedMove
10056         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10057         sprintf(string, partCommandString,
10058                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10059         commandOutput = popen(string, "r");
10060
10061         if (commandOutput == NULL) {
10062             DisplayError(_("Failed to invoke cmail"), 0);
10063         } else {
10064             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10065                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10066             }
10067             if (nBuffers > 1) {
10068                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10069                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10070                 nBytes = MSG_SIZ - 1;
10071             } else {
10072                 (void) memcpy(msg, buffer, nBytes);
10073             }
10074             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10075
10076             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10077                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10078
10079                 archived = TRUE;
10080                 for (i = 0; i < nCmailGames; i ++) {
10081                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10082                         archived = FALSE;
10083                     }
10084                 }
10085                 if (   archived
10086                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10087                         != NULL)) {
10088                     sprintf(buffer, "%s/%s.%s.archive",
10089                             arcDir,
10090                             appData.cmailGameName,
10091                             gameInfo.date);
10092                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10093                     cmailMsgLoaded = FALSE;
10094                 }
10095             }
10096
10097             DisplayInformation(msg);
10098             pclose(commandOutput);
10099         }
10100     } else {
10101         if ((*cmailMsg) != '\0') {
10102             DisplayInformation(cmailMsg);
10103         }
10104     }
10105
10106     return;
10107 #endif /* !WIN32 */
10108 }
10109
10110 char *
10111 CmailMsg()
10112 {
10113 #if WIN32
10114     return NULL;
10115 #else
10116     int  prependComma = 0;
10117     char number[5];
10118     char string[MSG_SIZ];       /* Space for game-list */
10119     int  i;
10120     
10121     if (!cmailMsgLoaded) return "";
10122
10123     if (cmailMailedMove) {
10124         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10125     } else {
10126         /* Create a list of games left */
10127         sprintf(string, "[");
10128         for (i = 0; i < nCmailGames; i ++) {
10129             if (! (   cmailMoveRegistered[i]
10130                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10131                 if (prependComma) {
10132                     sprintf(number, ",%d", i + 1);
10133                 } else {
10134                     sprintf(number, "%d", i + 1);
10135                     prependComma = 1;
10136                 }
10137                 
10138                 strcat(string, number);
10139             }
10140         }
10141         strcat(string, "]");
10142
10143         if (nCmailMovesRegistered + nCmailResults == 0) {
10144             switch (nCmailGames) {
10145               case 1:
10146                 sprintf(cmailMsg,
10147                         _("Still need to make move for game\n"));
10148                 break;
10149                 
10150               case 2:
10151                 sprintf(cmailMsg,
10152                         _("Still need to make moves for both games\n"));
10153                 break;
10154                 
10155               default:
10156                 sprintf(cmailMsg,
10157                         _("Still need to make moves for all %d games\n"),
10158                         nCmailGames);
10159                 break;
10160             }
10161         } else {
10162             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10163               case 1:
10164                 sprintf(cmailMsg,
10165                         _("Still need to make a move for game %s\n"),
10166                         string);
10167                 break;
10168                 
10169               case 0:
10170                 if (nCmailResults == nCmailGames) {
10171                     sprintf(cmailMsg, _("No unfinished games\n"));
10172                 } else {
10173                     sprintf(cmailMsg, _("Ready to send mail\n"));
10174                 }
10175                 break;
10176                 
10177               default:
10178                 sprintf(cmailMsg,
10179                         _("Still need to make moves for games %s\n"),
10180                         string);
10181             }
10182         }
10183     }
10184     return cmailMsg;
10185 #endif /* WIN32 */
10186 }
10187
10188 void
10189 ResetGameEvent()
10190 {
10191     if (gameMode == Training)
10192       SetTrainingModeOff();
10193
10194     Reset(TRUE, TRUE);
10195     cmailMsgLoaded = FALSE;
10196     if (appData.icsActive) {
10197       SendToICS(ics_prefix);
10198       SendToICS("refresh\n");
10199     }
10200 }
10201
10202 void
10203 ExitEvent(status)
10204      int status;
10205 {
10206     exiting++;
10207     if (exiting > 2) {
10208       /* Give up on clean exit */
10209       exit(status);
10210     }
10211     if (exiting > 1) {
10212       /* Keep trying for clean exit */
10213       return;
10214     }
10215
10216     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10217
10218     if (telnetISR != NULL) {
10219       RemoveInputSource(telnetISR);
10220     }
10221     if (icsPR != NoProc) {
10222       DestroyChildProcess(icsPR, TRUE);
10223     }
10224 #if 0
10225     /* Save game if resource set and not already saved by GameEnds() */
10226     if ((gameInfo.resultDetails == NULL || errorExitFlag )
10227                              && forwardMostMove > 0) {
10228       if (*appData.saveGameFile != NULLCHAR) {
10229         SaveGameToFile(appData.saveGameFile, TRUE);
10230       } else if (appData.autoSaveGames) {
10231         AutoSaveGame();
10232       }
10233       if (*appData.savePositionFile != NULLCHAR) {
10234         SavePositionToFile(appData.savePositionFile);
10235       }
10236     }
10237     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10238 #else
10239     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10240     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10241 #endif
10242     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10243     /* make sure this other one finishes before killing it!                  */
10244     if(endingGame) { int count = 0;
10245         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10246         while(endingGame && count++ < 10) DoSleep(1);
10247         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10248     }
10249
10250     /* Kill off chess programs */
10251     if (first.pr != NoProc) {
10252         ExitAnalyzeMode();
10253         
10254         DoSleep( appData.delayBeforeQuit );
10255         SendToProgram("quit\n", &first);
10256         DoSleep( appData.delayAfterQuit );
10257         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10258     }
10259     if (second.pr != NoProc) {
10260         DoSleep( appData.delayBeforeQuit );
10261         SendToProgram("quit\n", &second);
10262         DoSleep( appData.delayAfterQuit );
10263         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10264     }
10265     if (first.isr != NULL) {
10266         RemoveInputSource(first.isr);
10267     }
10268     if (second.isr != NULL) {
10269         RemoveInputSource(second.isr);
10270     }
10271
10272     ShutDownFrontEnd();
10273     exit(status);
10274 }
10275
10276 void
10277 PauseEvent()
10278 {
10279     if (appData.debugMode)
10280         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10281     if (pausing) {
10282         pausing = FALSE;
10283         ModeHighlight();
10284         if (gameMode == MachinePlaysWhite ||
10285             gameMode == MachinePlaysBlack) {
10286             StartClocks();
10287         } else {
10288             DisplayBothClocks();
10289         }
10290         if (gameMode == PlayFromGameFile) {
10291             if (appData.timeDelay >= 0) 
10292                 AutoPlayGameLoop();
10293         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10294             Reset(FALSE, TRUE);
10295             SendToICS(ics_prefix);
10296             SendToICS("refresh\n");
10297         } else if (currentMove < forwardMostMove) {
10298             ForwardInner(forwardMostMove);
10299         }
10300         pauseExamInvalid = FALSE;
10301     } else {
10302         switch (gameMode) {
10303           default:
10304             return;
10305           case IcsExamining:
10306             pauseExamForwardMostMove = forwardMostMove;
10307             pauseExamInvalid = FALSE;
10308             /* fall through */
10309           case IcsObserving:
10310           case IcsPlayingWhite:
10311           case IcsPlayingBlack:
10312             pausing = TRUE;
10313             ModeHighlight();
10314             return;
10315           case PlayFromGameFile:
10316             (void) StopLoadGameTimer();
10317             pausing = TRUE;
10318             ModeHighlight();
10319             break;
10320           case BeginningOfGame:
10321             if (appData.icsActive) return;
10322             /* else fall through */
10323           case MachinePlaysWhite:
10324           case MachinePlaysBlack:
10325           case TwoMachinesPlay:
10326             if (forwardMostMove == 0)
10327               return;           /* don't pause if no one has moved */
10328             if ((gameMode == MachinePlaysWhite &&
10329                  !WhiteOnMove(forwardMostMove)) ||
10330                 (gameMode == MachinePlaysBlack &&
10331                  WhiteOnMove(forwardMostMove))) {
10332                 StopClocks();
10333             }
10334             pausing = TRUE;
10335             ModeHighlight();
10336             break;
10337         }
10338     }
10339 }
10340
10341 void
10342 EditCommentEvent()
10343 {
10344     char title[MSG_SIZ];
10345
10346     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10347         strcpy(title, _("Edit comment"));
10348     } else {
10349         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10350                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10351                 parseList[currentMove - 1]);
10352     }
10353
10354     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10355 }
10356
10357
10358 void
10359 EditTagsEvent()
10360 {
10361     char *tags = PGNTags(&gameInfo);
10362     EditTagsPopUp(tags);
10363     free(tags);
10364 }
10365
10366 void
10367 AnalyzeModeEvent()
10368 {
10369     if (appData.noChessProgram || gameMode == AnalyzeMode)
10370       return;
10371
10372     if (gameMode != AnalyzeFile) {
10373         if (!appData.icsEngineAnalyze) {
10374                EditGameEvent();
10375                if (gameMode != EditGame) return;
10376         }
10377         ResurrectChessProgram();
10378         SendToProgram("analyze\n", &first);
10379         first.analyzing = TRUE;
10380         /*first.maybeThinking = TRUE;*/
10381         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10382         AnalysisPopUp(_("Analysis"),
10383                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10384     }
10385     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10386     pausing = FALSE;
10387     ModeHighlight();
10388     SetGameInfo();
10389
10390     StartAnalysisClock();
10391     GetTimeMark(&lastNodeCountTime);
10392     lastNodeCount = 0;
10393 }
10394
10395 void
10396 AnalyzeFileEvent()
10397 {
10398     if (appData.noChessProgram || gameMode == AnalyzeFile)
10399       return;
10400
10401     if (gameMode != AnalyzeMode) {
10402         EditGameEvent();
10403         if (gameMode != EditGame) return;
10404         ResurrectChessProgram();
10405         SendToProgram("analyze\n", &first);
10406         first.analyzing = TRUE;
10407         /*first.maybeThinking = TRUE;*/
10408         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10409         AnalysisPopUp(_("Analysis"),
10410                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10411     }
10412     gameMode = AnalyzeFile;
10413     pausing = FALSE;
10414     ModeHighlight();
10415     SetGameInfo();
10416
10417     StartAnalysisClock();
10418     GetTimeMark(&lastNodeCountTime);
10419     lastNodeCount = 0;
10420 }
10421
10422 void
10423 MachineWhiteEvent()
10424 {
10425     char buf[MSG_SIZ];
10426     char *bookHit = NULL;
10427
10428     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10429       return;
10430
10431
10432     if (gameMode == PlayFromGameFile || 
10433         gameMode == TwoMachinesPlay  || 
10434         gameMode == Training         || 
10435         gameMode == AnalyzeMode      || 
10436         gameMode == EndOfGame)
10437         EditGameEvent();
10438
10439     if (gameMode == EditPosition) 
10440         EditPositionDone();
10441
10442     if (!WhiteOnMove(currentMove)) {
10443         DisplayError(_("It is not White's turn"), 0);
10444         return;
10445     }
10446   
10447     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10448       ExitAnalyzeMode();
10449
10450     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10451         gameMode == AnalyzeFile)
10452         TruncateGame();
10453
10454     ResurrectChessProgram();    /* in case it isn't running */
10455     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10456         gameMode = MachinePlaysWhite;
10457         ResetClocks();
10458     } else
10459     gameMode = MachinePlaysWhite;
10460     pausing = FALSE;
10461     ModeHighlight();
10462     SetGameInfo();
10463     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10464     DisplayTitle(buf);
10465     if (first.sendName) {
10466       sprintf(buf, "name %s\n", gameInfo.black);
10467       SendToProgram(buf, &first);
10468     }
10469     if (first.sendTime) {
10470       if (first.useColors) {
10471         SendToProgram("black\n", &first); /*gnu kludge*/
10472       }
10473       SendTimeRemaining(&first, TRUE);
10474     }
10475     if (first.useColors) {
10476       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10477     }
10478     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10479     SetMachineThinkingEnables();
10480     first.maybeThinking = TRUE;
10481     StartClocks();
10482
10483     if (appData.autoFlipView && !flipView) {
10484       flipView = !flipView;
10485       DrawPosition(FALSE, NULL);
10486       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10487     }
10488
10489     if(bookHit) { // [HGM] book: simulate book reply
10490         static char bookMove[MSG_SIZ]; // a bit generous?
10491
10492         programStats.nodes = programStats.depth = programStats.time = 
10493         programStats.score = programStats.got_only_move = 0;
10494         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10495
10496         strcpy(bookMove, "move ");
10497         strcat(bookMove, bookHit);
10498         HandleMachineMove(bookMove, &first);
10499     }
10500 }
10501
10502 void
10503 MachineBlackEvent()
10504 {
10505     char buf[MSG_SIZ];
10506    char *bookHit = NULL;
10507
10508     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10509         return;
10510
10511
10512     if (gameMode == PlayFromGameFile || 
10513         gameMode == TwoMachinesPlay  || 
10514         gameMode == Training         || 
10515         gameMode == AnalyzeMode      || 
10516         gameMode == EndOfGame)
10517         EditGameEvent();
10518
10519     if (gameMode == EditPosition) 
10520         EditPositionDone();
10521
10522     if (WhiteOnMove(currentMove)) {
10523         DisplayError(_("It is not Black's turn"), 0);
10524         return;
10525     }
10526     
10527     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10528       ExitAnalyzeMode();
10529
10530     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10531         gameMode == AnalyzeFile)
10532         TruncateGame();
10533
10534     ResurrectChessProgram();    /* in case it isn't running */
10535     gameMode = MachinePlaysBlack;
10536     pausing = FALSE;
10537     ModeHighlight();
10538     SetGameInfo();
10539     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10540     DisplayTitle(buf);
10541     if (first.sendName) {
10542       sprintf(buf, "name %s\n", gameInfo.white);
10543       SendToProgram(buf, &first);
10544     }
10545     if (first.sendTime) {
10546       if (first.useColors) {
10547         SendToProgram("white\n", &first); /*gnu kludge*/
10548       }
10549       SendTimeRemaining(&first, FALSE);
10550     }
10551     if (first.useColors) {
10552       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10553     }
10554     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10555     SetMachineThinkingEnables();
10556     first.maybeThinking = TRUE;
10557     StartClocks();
10558
10559     if (appData.autoFlipView && flipView) {
10560       flipView = !flipView;
10561       DrawPosition(FALSE, NULL);
10562       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10563     }
10564     if(bookHit) { // [HGM] book: simulate book reply
10565         static char bookMove[MSG_SIZ]; // a bit generous?
10566
10567         programStats.nodes = programStats.depth = programStats.time = 
10568         programStats.score = programStats.got_only_move = 0;
10569         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10570
10571         strcpy(bookMove, "move ");
10572         strcat(bookMove, bookHit);
10573         HandleMachineMove(bookMove, &first);
10574     }
10575 }
10576
10577
10578 void
10579 DisplayTwoMachinesTitle()
10580 {
10581     char buf[MSG_SIZ];
10582     if (appData.matchGames > 0) {
10583         if (first.twoMachinesColor[0] == 'w') {
10584             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10585                     gameInfo.white, gameInfo.black,
10586                     first.matchWins, second.matchWins,
10587                     matchGame - 1 - (first.matchWins + second.matchWins));
10588         } else {
10589             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10590                     gameInfo.white, gameInfo.black,
10591                     second.matchWins, first.matchWins,
10592                     matchGame - 1 - (first.matchWins + second.matchWins));
10593         }
10594     } else {
10595         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10596     }
10597     DisplayTitle(buf);
10598 }
10599
10600 void
10601 TwoMachinesEvent P((void))
10602 {
10603     int i;
10604     char buf[MSG_SIZ];
10605     ChessProgramState *onmove;
10606     char *bookHit = NULL;
10607     
10608     if (appData.noChessProgram) return;
10609
10610     switch (gameMode) {
10611       case TwoMachinesPlay:
10612         return;
10613       case MachinePlaysWhite:
10614       case MachinePlaysBlack:
10615         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10616             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10617             return;
10618         }
10619         /* fall through */
10620       case BeginningOfGame:
10621       case PlayFromGameFile:
10622       case EndOfGame:
10623         EditGameEvent();
10624         if (gameMode != EditGame) return;
10625         break;
10626       case EditPosition:
10627         EditPositionDone();
10628         break;
10629       case AnalyzeMode:
10630       case AnalyzeFile:
10631         ExitAnalyzeMode();
10632         break;
10633       case EditGame:
10634       default:
10635         break;
10636     }
10637
10638     forwardMostMove = currentMove;
10639     ResurrectChessProgram();    /* in case first program isn't running */
10640
10641     if (second.pr == NULL) {
10642         StartChessProgram(&second);
10643         if (second.protocolVersion == 1) {
10644           TwoMachinesEventIfReady();
10645         } else {
10646           /* kludge: allow timeout for initial "feature" command */
10647           FreezeUI();
10648           DisplayMessage("", _("Starting second chess program"));
10649           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10650         }
10651         return;
10652     }
10653     DisplayMessage("", "");
10654     InitChessProgram(&second, FALSE);
10655     SendToProgram("force\n", &second);
10656     if (startedFromSetupPosition) {
10657         SendBoard(&second, backwardMostMove);
10658     if (appData.debugMode) {
10659         fprintf(debugFP, "Two Machines\n");
10660     }
10661     }
10662     for (i = backwardMostMove; i < forwardMostMove; i++) {
10663         SendMoveToProgram(i, &second);
10664     }
10665
10666     gameMode = TwoMachinesPlay;
10667     pausing = FALSE;
10668     ModeHighlight();
10669     SetGameInfo();
10670     DisplayTwoMachinesTitle();
10671     firstMove = TRUE;
10672     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10673         onmove = &first;
10674     } else {
10675         onmove = &second;
10676     }
10677
10678     SendToProgram(first.computerString, &first);
10679     if (first.sendName) {
10680       sprintf(buf, "name %s\n", second.tidy);
10681       SendToProgram(buf, &first);
10682     }
10683     SendToProgram(second.computerString, &second);
10684     if (second.sendName) {
10685       sprintf(buf, "name %s\n", first.tidy);
10686       SendToProgram(buf, &second);
10687     }
10688
10689     ResetClocks();
10690     if (!first.sendTime || !second.sendTime) {
10691         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10692         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10693     }
10694     if (onmove->sendTime) {
10695       if (onmove->useColors) {
10696         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10697       }
10698       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10699     }
10700     if (onmove->useColors) {
10701       SendToProgram(onmove->twoMachinesColor, onmove);
10702     }
10703     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10704 //    SendToProgram("go\n", onmove);
10705     onmove->maybeThinking = TRUE;
10706     SetMachineThinkingEnables();
10707
10708     StartClocks();
10709
10710     if(bookHit) { // [HGM] book: simulate book reply
10711         static char bookMove[MSG_SIZ]; // a bit generous?
10712
10713         programStats.nodes = programStats.depth = programStats.time = 
10714         programStats.score = programStats.got_only_move = 0;
10715         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10716
10717         strcpy(bookMove, "move ");
10718         strcat(bookMove, bookHit);
10719         HandleMachineMove(bookMove, &first);
10720     }
10721 }
10722
10723 void
10724 TrainingEvent()
10725 {
10726     if (gameMode == Training) {
10727       SetTrainingModeOff();
10728       gameMode = PlayFromGameFile;
10729       DisplayMessage("", _("Training mode off"));
10730     } else {
10731       gameMode = Training;
10732       animateTraining = appData.animate;
10733
10734       /* make sure we are not already at the end of the game */
10735       if (currentMove < forwardMostMove) {
10736         SetTrainingModeOn();
10737         DisplayMessage("", _("Training mode on"));
10738       } else {
10739         gameMode = PlayFromGameFile;
10740         DisplayError(_("Already at end of game"), 0);
10741       }
10742     }
10743     ModeHighlight();
10744 }
10745
10746 void
10747 IcsClientEvent()
10748 {
10749     if (!appData.icsActive) return;
10750     switch (gameMode) {
10751       case IcsPlayingWhite:
10752       case IcsPlayingBlack:
10753       case IcsObserving:
10754       case IcsIdle:
10755       case BeginningOfGame:
10756       case IcsExamining:
10757         return;
10758
10759       case EditGame:
10760         break;
10761
10762       case EditPosition:
10763         EditPositionDone();
10764         break;
10765
10766       case AnalyzeMode:
10767       case AnalyzeFile:
10768         ExitAnalyzeMode();
10769         break;
10770         
10771       default:
10772         EditGameEvent();
10773         break;
10774     }
10775
10776     gameMode = IcsIdle;
10777     ModeHighlight();
10778     return;
10779 }
10780
10781
10782 void
10783 EditGameEvent()
10784 {
10785     int i;
10786
10787     switch (gameMode) {
10788       case Training:
10789         SetTrainingModeOff();
10790         break;
10791       case MachinePlaysWhite:
10792       case MachinePlaysBlack:
10793       case BeginningOfGame:
10794         SendToProgram("force\n", &first);
10795         SetUserThinkingEnables();
10796         break;
10797       case PlayFromGameFile:
10798         (void) StopLoadGameTimer();
10799         if (gameFileFP != NULL) {
10800             gameFileFP = NULL;
10801         }
10802         break;
10803       case EditPosition:
10804         EditPositionDone();
10805         break;
10806       case AnalyzeMode:
10807       case AnalyzeFile:
10808         ExitAnalyzeMode();
10809         SendToProgram("force\n", &first);
10810         break;
10811       case TwoMachinesPlay:
10812         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10813         ResurrectChessProgram();
10814         SetUserThinkingEnables();
10815         break;
10816       case EndOfGame:
10817         ResurrectChessProgram();
10818         break;
10819       case IcsPlayingBlack:
10820       case IcsPlayingWhite:
10821         DisplayError(_("Warning: You are still playing a game"), 0);
10822         break;
10823       case IcsObserving:
10824         DisplayError(_("Warning: You are still observing a game"), 0);
10825         break;
10826       case IcsExamining:
10827         DisplayError(_("Warning: You are still examining a game"), 0);
10828         break;
10829       case IcsIdle:
10830         break;
10831       case EditGame:
10832       default:
10833         return;
10834     }
10835     
10836     pausing = FALSE;
10837     StopClocks();
10838     first.offeredDraw = second.offeredDraw = 0;
10839
10840     if (gameMode == PlayFromGameFile) {
10841         whiteTimeRemaining = timeRemaining[0][currentMove];
10842         blackTimeRemaining = timeRemaining[1][currentMove];
10843         DisplayTitle("");
10844     }
10845
10846     if (gameMode == MachinePlaysWhite ||
10847         gameMode == MachinePlaysBlack ||
10848         gameMode == TwoMachinesPlay ||
10849         gameMode == EndOfGame) {
10850         i = forwardMostMove;
10851         while (i > currentMove) {
10852             SendToProgram("undo\n", &first);
10853             i--;
10854         }
10855         whiteTimeRemaining = timeRemaining[0][currentMove];
10856         blackTimeRemaining = timeRemaining[1][currentMove];
10857         DisplayBothClocks();
10858         if (whiteFlag || blackFlag) {
10859             whiteFlag = blackFlag = 0;
10860         }
10861         DisplayTitle("");
10862     }           
10863     
10864     gameMode = EditGame;
10865     ModeHighlight();
10866     SetGameInfo();
10867 }
10868
10869
10870 void
10871 EditPositionEvent()
10872 {
10873     if (gameMode == EditPosition) {
10874         EditGameEvent();
10875         return;
10876     }
10877     
10878     EditGameEvent();
10879     if (gameMode != EditGame) return;
10880     
10881     gameMode = EditPosition;
10882     ModeHighlight();
10883     SetGameInfo();
10884     if (currentMove > 0)
10885       CopyBoard(boards[0], boards[currentMove]);
10886     
10887     blackPlaysFirst = !WhiteOnMove(currentMove);
10888     ResetClocks();
10889     currentMove = forwardMostMove = backwardMostMove = 0;
10890     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10891     DisplayMove(-1);
10892 }
10893
10894 void
10895 ExitAnalyzeMode()
10896 {
10897     /* [DM] icsEngineAnalyze - possible call from other functions */
10898     if (appData.icsEngineAnalyze) {
10899         appData.icsEngineAnalyze = FALSE;
10900
10901         DisplayMessage("",_("Close ICS engine analyze..."));
10902     }
10903     if (first.analysisSupport && first.analyzing) {
10904       SendToProgram("exit\n", &first);
10905       first.analyzing = FALSE;
10906     }
10907     AnalysisPopDown();
10908     thinkOutput[0] = NULLCHAR;
10909 }
10910
10911 void
10912 EditPositionDone()
10913 {
10914     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
10915
10916     startedFromSetupPosition = TRUE;
10917     InitChessProgram(&first, FALSE);
10918     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
10919     if(boards[0][0][BOARD_WIDTH>>1] == king) {
10920         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
10921         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
10922     } else castlingRights[0][2] = -1;
10923     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
10924         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
10925         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
10926     } else castlingRights[0][5] = -1;
10927     SendToProgram("force\n", &first);
10928     if (blackPlaysFirst) {
10929         strcpy(moveList[0], "");
10930         strcpy(parseList[0], "");
10931         currentMove = forwardMostMove = backwardMostMove = 1;
10932         CopyBoard(boards[1], boards[0]);
10933         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10934         { int i;
10935           epStatus[1] = epStatus[0];
10936           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10937         }
10938     } else {
10939         currentMove = forwardMostMove = backwardMostMove = 0;
10940     }
10941     SendBoard(&first, forwardMostMove);
10942     if (appData.debugMode) {
10943         fprintf(debugFP, "EditPosDone\n");
10944     }
10945     DisplayTitle("");
10946     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10947     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10948     gameMode = EditGame;
10949     ModeHighlight();
10950     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10951     ClearHighlights(); /* [AS] */
10952 }
10953
10954 /* Pause for `ms' milliseconds */
10955 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10956 void
10957 TimeDelay(ms)
10958      long ms;
10959 {
10960     TimeMark m1, m2;
10961
10962     GetTimeMark(&m1);
10963     do {
10964         GetTimeMark(&m2);
10965     } while (SubtractTimeMarks(&m2, &m1) < ms);
10966 }
10967
10968 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10969 void
10970 SendMultiLineToICS(buf)
10971      char *buf;
10972 {
10973     char temp[MSG_SIZ+1], *p;
10974     int len;
10975
10976     len = strlen(buf);
10977     if (len > MSG_SIZ)
10978       len = MSG_SIZ;
10979   
10980     strncpy(temp, buf, len);
10981     temp[len] = 0;
10982
10983     p = temp;
10984     while (*p) {
10985         if (*p == '\n' || *p == '\r')
10986           *p = ' ';
10987         ++p;
10988     }
10989
10990     strcat(temp, "\n");
10991     SendToICS(temp);
10992     SendToPlayer(temp, strlen(temp));
10993 }
10994
10995 void
10996 SetWhiteToPlayEvent()
10997 {
10998     if (gameMode == EditPosition) {
10999         blackPlaysFirst = FALSE;
11000         DisplayBothClocks();    /* works because currentMove is 0 */
11001     } else if (gameMode == IcsExamining) {
11002         SendToICS(ics_prefix);
11003         SendToICS("tomove white\n");
11004     }
11005 }
11006
11007 void
11008 SetBlackToPlayEvent()
11009 {
11010     if (gameMode == EditPosition) {
11011         blackPlaysFirst = TRUE;
11012         currentMove = 1;        /* kludge */
11013         DisplayBothClocks();
11014         currentMove = 0;
11015     } else if (gameMode == IcsExamining) {
11016         SendToICS(ics_prefix);
11017         SendToICS("tomove black\n");
11018     }
11019 }
11020
11021 void
11022 EditPositionMenuEvent(selection, x, y)
11023      ChessSquare selection;
11024      int x, y;
11025 {
11026     char buf[MSG_SIZ];
11027     ChessSquare piece = boards[0][y][x];
11028
11029     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11030
11031     switch (selection) {
11032       case ClearBoard:
11033         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11034             SendToICS(ics_prefix);
11035             SendToICS("bsetup clear\n");
11036         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11037             SendToICS(ics_prefix);
11038             SendToICS("clearboard\n");
11039         } else {
11040             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11041                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11042                 for (y = 0; y < BOARD_HEIGHT; y++) {
11043                     if (gameMode == IcsExamining) {
11044                         if (boards[currentMove][y][x] != EmptySquare) {
11045                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11046                                     AAA + x, ONE + y);
11047                             SendToICS(buf);
11048                         }
11049                     } else {
11050                         boards[0][y][x] = p;
11051                     }
11052                 }
11053             }
11054         }
11055         if (gameMode == EditPosition) {
11056             DrawPosition(FALSE, boards[0]);
11057         }
11058         break;
11059
11060       case WhitePlay:
11061         SetWhiteToPlayEvent();
11062         break;
11063
11064       case BlackPlay:
11065         SetBlackToPlayEvent();
11066         break;
11067
11068       case EmptySquare:
11069         if (gameMode == IcsExamining) {
11070             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11071             SendToICS(buf);
11072         } else {
11073             boards[0][y][x] = EmptySquare;
11074             DrawPosition(FALSE, boards[0]);
11075         }
11076         break;
11077
11078       case PromotePiece:
11079         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11080            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11081             selection = (ChessSquare) (PROMOTED piece);
11082         } else if(piece == EmptySquare) selection = WhiteSilver;
11083         else selection = (ChessSquare)((int)piece - 1);
11084         goto defaultlabel;
11085
11086       case DemotePiece:
11087         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11088            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11089             selection = (ChessSquare) (DEMOTED piece);
11090         } else if(piece == EmptySquare) selection = BlackSilver;
11091         else selection = (ChessSquare)((int)piece + 1);       
11092         goto defaultlabel;
11093
11094       case WhiteQueen:
11095       case BlackQueen:
11096         if(gameInfo.variant == VariantShatranj ||
11097            gameInfo.variant == VariantXiangqi  ||
11098            gameInfo.variant == VariantCourier    )
11099             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11100         goto defaultlabel;
11101
11102       case WhiteKing:
11103       case BlackKing:
11104         if(gameInfo.variant == VariantXiangqi)
11105             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11106         if(gameInfo.variant == VariantKnightmate)
11107             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11108       default:
11109         defaultlabel:
11110         if (gameMode == IcsExamining) {
11111             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11112                     PieceToChar(selection), AAA + x, ONE + y);
11113             SendToICS(buf);
11114         } else {
11115             boards[0][y][x] = selection;
11116             DrawPosition(FALSE, boards[0]);
11117         }
11118         break;
11119     }
11120 }
11121
11122
11123 void
11124 DropMenuEvent(selection, x, y)
11125      ChessSquare selection;
11126      int x, y;
11127 {
11128     ChessMove moveType;
11129
11130     switch (gameMode) {
11131       case IcsPlayingWhite:
11132       case MachinePlaysBlack:
11133         if (!WhiteOnMove(currentMove)) {
11134             DisplayMoveError(_("It is Black's turn"));
11135             return;
11136         }
11137         moveType = WhiteDrop;
11138         break;
11139       case IcsPlayingBlack:
11140       case MachinePlaysWhite:
11141         if (WhiteOnMove(currentMove)) {
11142             DisplayMoveError(_("It is White's turn"));
11143             return;
11144         }
11145         moveType = BlackDrop;
11146         break;
11147       case EditGame:
11148         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11149         break;
11150       default:
11151         return;
11152     }
11153
11154     if (moveType == BlackDrop && selection < BlackPawn) {
11155       selection = (ChessSquare) ((int) selection
11156                                  + (int) BlackPawn - (int) WhitePawn);
11157     }
11158     if (boards[currentMove][y][x] != EmptySquare) {
11159         DisplayMoveError(_("That square is occupied"));
11160         return;
11161     }
11162
11163     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11164 }
11165
11166 void
11167 AcceptEvent()
11168 {
11169     /* Accept a pending offer of any kind from opponent */
11170     
11171     if (appData.icsActive) {
11172         SendToICS(ics_prefix);
11173         SendToICS("accept\n");
11174     } else if (cmailMsgLoaded) {
11175         if (currentMove == cmailOldMove &&
11176             commentList[cmailOldMove] != NULL &&
11177             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11178                    "Black offers a draw" : "White offers a draw")) {
11179             TruncateGame();
11180             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11181             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11182         } else {
11183             DisplayError(_("There is no pending offer on this move"), 0);
11184             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11185         }
11186     } else {
11187         /* Not used for offers from chess program */
11188     }
11189 }
11190
11191 void
11192 DeclineEvent()
11193 {
11194     /* Decline a pending offer of any kind from opponent */
11195     
11196     if (appData.icsActive) {
11197         SendToICS(ics_prefix);
11198         SendToICS("decline\n");
11199     } else if (cmailMsgLoaded) {
11200         if (currentMove == cmailOldMove &&
11201             commentList[cmailOldMove] != NULL &&
11202             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11203                    "Black offers a draw" : "White offers a draw")) {
11204 #ifdef NOTDEF
11205             AppendComment(cmailOldMove, "Draw declined");
11206             DisplayComment(cmailOldMove - 1, "Draw declined");
11207 #endif /*NOTDEF*/
11208         } else {
11209             DisplayError(_("There is no pending offer on this move"), 0);
11210         }
11211     } else {
11212         /* Not used for offers from chess program */
11213     }
11214 }
11215
11216 void
11217 RematchEvent()
11218 {
11219     /* Issue ICS rematch command */
11220     if (appData.icsActive) {
11221         SendToICS(ics_prefix);
11222         SendToICS("rematch\n");
11223     }
11224 }
11225
11226 void
11227 CallFlagEvent()
11228 {
11229     /* Call your opponent's flag (claim a win on time) */
11230     if (appData.icsActive) {
11231         SendToICS(ics_prefix);
11232         SendToICS("flag\n");
11233     } else {
11234         switch (gameMode) {
11235           default:
11236             return;
11237           case MachinePlaysWhite:
11238             if (whiteFlag) {
11239                 if (blackFlag)
11240                   GameEnds(GameIsDrawn, "Both players ran out of time",
11241                            GE_PLAYER);
11242                 else
11243                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11244             } else {
11245                 DisplayError(_("Your opponent is not out of time"), 0);
11246             }
11247             break;
11248           case MachinePlaysBlack:
11249             if (blackFlag) {
11250                 if (whiteFlag)
11251                   GameEnds(GameIsDrawn, "Both players ran out of time",
11252                            GE_PLAYER);
11253                 else
11254                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11255             } else {
11256                 DisplayError(_("Your opponent is not out of time"), 0);
11257             }
11258             break;
11259         }
11260     }
11261 }
11262
11263 void
11264 DrawEvent()
11265 {
11266     /* Offer draw or accept pending draw offer from opponent */
11267     
11268     if (appData.icsActive) {
11269         /* Note: tournament rules require draw offers to be
11270            made after you make your move but before you punch
11271            your clock.  Currently ICS doesn't let you do that;
11272            instead, you immediately punch your clock after making
11273            a move, but you can offer a draw at any time. */
11274         
11275         SendToICS(ics_prefix);
11276         SendToICS("draw\n");
11277     } else if (cmailMsgLoaded) {
11278         if (currentMove == cmailOldMove &&
11279             commentList[cmailOldMove] != NULL &&
11280             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11281                    "Black offers a draw" : "White offers a draw")) {
11282             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11283             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11284         } else if (currentMove == cmailOldMove + 1) {
11285             char *offer = WhiteOnMove(cmailOldMove) ?
11286               "White offers a draw" : "Black offers a draw";
11287             AppendComment(currentMove, offer);
11288             DisplayComment(currentMove - 1, offer);
11289             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11290         } else {
11291             DisplayError(_("You must make your move before offering a draw"), 0);
11292             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11293         }
11294     } else if (first.offeredDraw) {
11295         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11296     } else {
11297         if (first.sendDrawOffers) {
11298             SendToProgram("draw\n", &first);
11299             userOfferedDraw = TRUE;
11300         }
11301     }
11302 }
11303
11304 void
11305 AdjournEvent()
11306 {
11307     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11308     
11309     if (appData.icsActive) {
11310         SendToICS(ics_prefix);
11311         SendToICS("adjourn\n");
11312     } else {
11313         /* Currently GNU Chess doesn't offer or accept Adjourns */
11314     }
11315 }
11316
11317
11318 void
11319 AbortEvent()
11320 {
11321     /* Offer Abort or accept pending Abort offer from opponent */
11322     
11323     if (appData.icsActive) {
11324         SendToICS(ics_prefix);
11325         SendToICS("abort\n");
11326     } else {
11327         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11328     }
11329 }
11330
11331 void
11332 ResignEvent()
11333 {
11334     /* Resign.  You can do this even if it's not your turn. */
11335     
11336     if (appData.icsActive) {
11337         SendToICS(ics_prefix);
11338         SendToICS("resign\n");
11339     } else {
11340         switch (gameMode) {
11341           case MachinePlaysWhite:
11342             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11343             break;
11344           case MachinePlaysBlack:
11345             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11346             break;
11347           case EditGame:
11348             if (cmailMsgLoaded) {
11349                 TruncateGame();
11350                 if (WhiteOnMove(cmailOldMove)) {
11351                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11352                 } else {
11353                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11354                 }
11355                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11356             }
11357             break;
11358           default:
11359             break;
11360         }
11361     }
11362 }
11363
11364
11365 void
11366 StopObservingEvent()
11367 {
11368     /* Stop observing current games */
11369     SendToICS(ics_prefix);
11370     SendToICS("unobserve\n");
11371 }
11372
11373 void
11374 StopExaminingEvent()
11375 {
11376     /* Stop observing current game */
11377     SendToICS(ics_prefix);
11378     SendToICS("unexamine\n");
11379 }
11380
11381 void
11382 ForwardInner(target)
11383      int target;
11384 {
11385     int limit;
11386
11387     if (appData.debugMode)
11388         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11389                 target, currentMove, forwardMostMove);
11390
11391     if (gameMode == EditPosition)
11392       return;
11393
11394     if (gameMode == PlayFromGameFile && !pausing)
11395       PauseEvent();
11396     
11397     if (gameMode == IcsExamining && pausing)
11398       limit = pauseExamForwardMostMove;
11399     else
11400       limit = forwardMostMove;
11401     
11402     if (target > limit) target = limit;
11403
11404     if (target > 0 && moveList[target - 1][0]) {
11405         int fromX, fromY, toX, toY;
11406         toX = moveList[target - 1][2] - AAA;
11407         toY = moveList[target - 1][3] - ONE;
11408         if (moveList[target - 1][1] == '@') {
11409             if (appData.highlightLastMove) {
11410                 SetHighlights(-1, -1, toX, toY);
11411             }
11412         } else {
11413             fromX = moveList[target - 1][0] - AAA;
11414             fromY = moveList[target - 1][1] - ONE;
11415             if (target == currentMove + 1) {
11416                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11417             }
11418             if (appData.highlightLastMove) {
11419                 SetHighlights(fromX, fromY, toX, toY);
11420             }
11421         }
11422     }
11423     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11424         gameMode == Training || gameMode == PlayFromGameFile || 
11425         gameMode == AnalyzeFile) {
11426         while (currentMove < target) {
11427             SendMoveToProgram(currentMove++, &first);
11428         }
11429     } else {
11430         currentMove = target;
11431     }
11432     
11433     if (gameMode == EditGame || gameMode == EndOfGame) {
11434         whiteTimeRemaining = timeRemaining[0][currentMove];
11435         blackTimeRemaining = timeRemaining[1][currentMove];
11436     }
11437     DisplayBothClocks();
11438     DisplayMove(currentMove - 1);
11439     DrawPosition(FALSE, boards[currentMove]);
11440     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11441     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11442         DisplayComment(currentMove - 1, commentList[currentMove]);
11443     }
11444 }
11445
11446
11447 void
11448 ForwardEvent()
11449 {
11450     if (gameMode == IcsExamining && !pausing) {
11451         SendToICS(ics_prefix);
11452         SendToICS("forward\n");
11453     } else {
11454         ForwardInner(currentMove + 1);
11455     }
11456 }
11457
11458 void
11459 ToEndEvent()
11460 {
11461     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11462         /* to optimze, we temporarily turn off analysis mode while we feed
11463          * the remaining moves to the engine. Otherwise we get analysis output
11464          * after each move.
11465          */ 
11466         if (first.analysisSupport) {
11467           SendToProgram("exit\nforce\n", &first);
11468           first.analyzing = FALSE;
11469         }
11470     }
11471         
11472     if (gameMode == IcsExamining && !pausing) {
11473         SendToICS(ics_prefix);
11474         SendToICS("forward 999999\n");
11475     } else {
11476         ForwardInner(forwardMostMove);
11477     }
11478
11479     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11480         /* we have fed all the moves, so reactivate analysis mode */
11481         SendToProgram("analyze\n", &first);
11482         first.analyzing = TRUE;
11483         /*first.maybeThinking = TRUE;*/
11484         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11485     }
11486 }
11487
11488 void
11489 BackwardInner(target)
11490      int target;
11491 {
11492     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11493
11494     if (appData.debugMode)
11495         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11496                 target, currentMove, forwardMostMove);
11497
11498     if (gameMode == EditPosition) return;
11499     if (currentMove <= backwardMostMove) {
11500         ClearHighlights();
11501         DrawPosition(full_redraw, boards[currentMove]);
11502         return;
11503     }
11504     if (gameMode == PlayFromGameFile && !pausing)
11505       PauseEvent();
11506     
11507     if (moveList[target][0]) {
11508         int fromX, fromY, toX, toY;
11509         toX = moveList[target][2] - AAA;
11510         toY = moveList[target][3] - ONE;
11511         if (moveList[target][1] == '@') {
11512             if (appData.highlightLastMove) {
11513                 SetHighlights(-1, -1, toX, toY);
11514             }
11515         } else {
11516             fromX = moveList[target][0] - AAA;
11517             fromY = moveList[target][1] - ONE;
11518             if (target == currentMove - 1) {
11519                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11520             }
11521             if (appData.highlightLastMove) {
11522                 SetHighlights(fromX, fromY, toX, toY);
11523             }
11524         }
11525     }
11526     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11527         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11528         while (currentMove > target) {
11529             SendToProgram("undo\n", &first);
11530             currentMove--;
11531         }
11532     } else {
11533         currentMove = target;
11534     }
11535     
11536     if (gameMode == EditGame || gameMode == EndOfGame) {
11537         whiteTimeRemaining = timeRemaining[0][currentMove];
11538         blackTimeRemaining = timeRemaining[1][currentMove];
11539     }
11540     DisplayBothClocks();
11541     DisplayMove(currentMove - 1);
11542     DrawPosition(full_redraw, boards[currentMove]);
11543     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11544     // [HGM] PV info: routine tests if comment empty
11545     DisplayComment(currentMove - 1, commentList[currentMove]);
11546 }
11547
11548 void
11549 BackwardEvent()
11550 {
11551     if (gameMode == IcsExamining && !pausing) {
11552         SendToICS(ics_prefix);
11553         SendToICS("backward\n");
11554     } else {
11555         BackwardInner(currentMove - 1);
11556     }
11557 }
11558
11559 void
11560 ToStartEvent()
11561 {
11562     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11563         /* to optimze, we temporarily turn off analysis mode while we undo
11564          * all the moves. Otherwise we get analysis output after each undo.
11565          */ 
11566         if (first.analysisSupport) {
11567           SendToProgram("exit\nforce\n", &first);
11568           first.analyzing = FALSE;
11569         }
11570     }
11571
11572     if (gameMode == IcsExamining && !pausing) {
11573         SendToICS(ics_prefix);
11574         SendToICS("backward 999999\n");
11575     } else {
11576         BackwardInner(backwardMostMove);
11577     }
11578
11579     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11580         /* we have fed all the moves, so reactivate analysis mode */
11581         SendToProgram("analyze\n", &first);
11582         first.analyzing = TRUE;
11583         /*first.maybeThinking = TRUE;*/
11584         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11585     }
11586 }
11587
11588 void
11589 ToNrEvent(int to)
11590 {
11591   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11592   if (to >= forwardMostMove) to = forwardMostMove;
11593   if (to <= backwardMostMove) to = backwardMostMove;
11594   if (to < currentMove) {
11595     BackwardInner(to);
11596   } else {
11597     ForwardInner(to);
11598   }
11599 }
11600
11601 void
11602 RevertEvent()
11603 {
11604     if (gameMode != IcsExamining) {
11605         DisplayError(_("You are not examining a game"), 0);
11606         return;
11607     }
11608     if (pausing) {
11609         DisplayError(_("You can't revert while pausing"), 0);
11610         return;
11611     }
11612     SendToICS(ics_prefix);
11613     SendToICS("revert\n");
11614 }
11615
11616 void
11617 RetractMoveEvent()
11618 {
11619     switch (gameMode) {
11620       case MachinePlaysWhite:
11621       case MachinePlaysBlack:
11622         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11623             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11624             return;
11625         }
11626         if (forwardMostMove < 2) return;
11627         currentMove = forwardMostMove = forwardMostMove - 2;
11628         whiteTimeRemaining = timeRemaining[0][currentMove];
11629         blackTimeRemaining = timeRemaining[1][currentMove];
11630         DisplayBothClocks();
11631         DisplayMove(currentMove - 1);
11632         ClearHighlights();/*!! could figure this out*/
11633         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11634         SendToProgram("remove\n", &first);
11635         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11636         break;
11637
11638       case BeginningOfGame:
11639       default:
11640         break;
11641
11642       case IcsPlayingWhite:
11643       case IcsPlayingBlack:
11644         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11645             SendToICS(ics_prefix);
11646             SendToICS("takeback 2\n");
11647         } else {
11648             SendToICS(ics_prefix);
11649             SendToICS("takeback 1\n");
11650         }
11651         break;
11652     }
11653 }
11654
11655 void
11656 MoveNowEvent()
11657 {
11658     ChessProgramState *cps;
11659
11660     switch (gameMode) {
11661       case MachinePlaysWhite:
11662         if (!WhiteOnMove(forwardMostMove)) {
11663             DisplayError(_("It is your turn"), 0);
11664             return;
11665         }
11666         cps = &first;
11667         break;
11668       case MachinePlaysBlack:
11669         if (WhiteOnMove(forwardMostMove)) {
11670             DisplayError(_("It is your turn"), 0);
11671             return;
11672         }
11673         cps = &first;
11674         break;
11675       case TwoMachinesPlay:
11676         if (WhiteOnMove(forwardMostMove) ==
11677             (first.twoMachinesColor[0] == 'w')) {
11678             cps = &first;
11679         } else {
11680             cps = &second;
11681         }
11682         break;
11683       case BeginningOfGame:
11684       default:
11685         return;
11686     }
11687     SendToProgram("?\n", cps);
11688 }
11689
11690 void
11691 TruncateGameEvent()
11692 {
11693     EditGameEvent();
11694     if (gameMode != EditGame) return;
11695     TruncateGame();
11696 }
11697
11698 void
11699 TruncateGame()
11700 {
11701     if (forwardMostMove > currentMove) {
11702         if (gameInfo.resultDetails != NULL) {
11703             free(gameInfo.resultDetails);
11704             gameInfo.resultDetails = NULL;
11705             gameInfo.result = GameUnfinished;
11706         }
11707         forwardMostMove = currentMove;
11708         HistorySet(parseList, backwardMostMove, forwardMostMove,
11709                    currentMove-1);
11710     }
11711 }
11712
11713 void
11714 HintEvent()
11715 {
11716     if (appData.noChessProgram) return;
11717     switch (gameMode) {
11718       case MachinePlaysWhite:
11719         if (WhiteOnMove(forwardMostMove)) {
11720             DisplayError(_("Wait until your turn"), 0);
11721             return;
11722         }
11723         break;
11724       case BeginningOfGame:
11725       case MachinePlaysBlack:
11726         if (!WhiteOnMove(forwardMostMove)) {
11727             DisplayError(_("Wait until your turn"), 0);
11728             return;
11729         }
11730         break;
11731       default:
11732         DisplayError(_("No hint available"), 0);
11733         return;
11734     }
11735     SendToProgram("hint\n", &first);
11736     hintRequested = TRUE;
11737 }
11738
11739 void
11740 BookEvent()
11741 {
11742     if (appData.noChessProgram) return;
11743     switch (gameMode) {
11744       case MachinePlaysWhite:
11745         if (WhiteOnMove(forwardMostMove)) {
11746             DisplayError(_("Wait until your turn"), 0);
11747             return;
11748         }
11749         break;
11750       case BeginningOfGame:
11751       case MachinePlaysBlack:
11752         if (!WhiteOnMove(forwardMostMove)) {
11753             DisplayError(_("Wait until your turn"), 0);
11754             return;
11755         }
11756         break;
11757       case EditPosition:
11758         EditPositionDone();
11759         break;
11760       case TwoMachinesPlay:
11761         return;
11762       default:
11763         break;
11764     }
11765     SendToProgram("bk\n", &first);
11766     bookOutput[0] = NULLCHAR;
11767     bookRequested = TRUE;
11768 }
11769
11770 void
11771 AboutGameEvent()
11772 {
11773     char *tags = PGNTags(&gameInfo);
11774     TagsPopUp(tags, CmailMsg());
11775     free(tags);
11776 }
11777
11778 /* end button procedures */
11779
11780 void
11781 PrintPosition(fp, move)
11782      FILE *fp;
11783      int move;
11784 {
11785     int i, j;
11786     
11787     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11788         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11789             char c = PieceToChar(boards[move][i][j]);
11790             fputc(c == 'x' ? '.' : c, fp);
11791             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11792         }
11793     }
11794     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11795       fprintf(fp, "white to play\n");
11796     else
11797       fprintf(fp, "black to play\n");
11798 }
11799
11800 void
11801 PrintOpponents(fp)
11802      FILE *fp;
11803 {
11804     if (gameInfo.white != NULL) {
11805         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11806     } else {
11807         fprintf(fp, "\n");
11808     }
11809 }
11810
11811 /* Find last component of program's own name, using some heuristics */
11812 void
11813 TidyProgramName(prog, host, buf)
11814      char *prog, *host, buf[MSG_SIZ];
11815 {
11816     char *p, *q;
11817     int local = (strcmp(host, "localhost") == 0);
11818     while (!local && (p = strchr(prog, ';')) != NULL) {
11819         p++;
11820         while (*p == ' ') p++;
11821         prog = p;
11822     }
11823     if (*prog == '"' || *prog == '\'') {
11824         q = strchr(prog + 1, *prog);
11825     } else {
11826         q = strchr(prog, ' ');
11827     }
11828     if (q == NULL) q = prog + strlen(prog);
11829     p = q;
11830     while (p >= prog && *p != '/' && *p != '\\') p--;
11831     p++;
11832     if(p == prog && *p == '"') p++;
11833     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11834     memcpy(buf, p, q - p);
11835     buf[q - p] = NULLCHAR;
11836     if (!local) {
11837         strcat(buf, "@");
11838         strcat(buf, host);
11839     }
11840 }
11841
11842 char *
11843 TimeControlTagValue()
11844 {
11845     char buf[MSG_SIZ];
11846     if (!appData.clockMode) {
11847         strcpy(buf, "-");
11848     } else if (movesPerSession > 0) {
11849         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11850     } else if (timeIncrement == 0) {
11851         sprintf(buf, "%ld", timeControl/1000);
11852     } else {
11853         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11854     }
11855     return StrSave(buf);
11856 }
11857
11858 void
11859 SetGameInfo()
11860 {
11861     /* This routine is used only for certain modes */
11862     VariantClass v = gameInfo.variant;
11863     ClearGameInfo(&gameInfo);
11864     gameInfo.variant = v;
11865
11866     switch (gameMode) {
11867       case MachinePlaysWhite:
11868         gameInfo.event = StrSave( appData.pgnEventHeader );
11869         gameInfo.site = StrSave(HostName());
11870         gameInfo.date = PGNDate();
11871         gameInfo.round = StrSave("-");
11872         gameInfo.white = StrSave(first.tidy);
11873         gameInfo.black = StrSave(UserName());
11874         gameInfo.timeControl = TimeControlTagValue();
11875         break;
11876
11877       case MachinePlaysBlack:
11878         gameInfo.event = StrSave( appData.pgnEventHeader );
11879         gameInfo.site = StrSave(HostName());
11880         gameInfo.date = PGNDate();
11881         gameInfo.round = StrSave("-");
11882         gameInfo.white = StrSave(UserName());
11883         gameInfo.black = StrSave(first.tidy);
11884         gameInfo.timeControl = TimeControlTagValue();
11885         break;
11886
11887       case TwoMachinesPlay:
11888         gameInfo.event = StrSave( appData.pgnEventHeader );
11889         gameInfo.site = StrSave(HostName());
11890         gameInfo.date = PGNDate();
11891         if (matchGame > 0) {
11892             char buf[MSG_SIZ];
11893             sprintf(buf, "%d", matchGame);
11894             gameInfo.round = StrSave(buf);
11895         } else {
11896             gameInfo.round = StrSave("-");
11897         }
11898         if (first.twoMachinesColor[0] == 'w') {
11899             gameInfo.white = StrSave(first.tidy);
11900             gameInfo.black = StrSave(second.tidy);
11901         } else {
11902             gameInfo.white = StrSave(second.tidy);
11903             gameInfo.black = StrSave(first.tidy);
11904         }
11905         gameInfo.timeControl = TimeControlTagValue();
11906         break;
11907
11908       case EditGame:
11909         gameInfo.event = StrSave("Edited game");
11910         gameInfo.site = StrSave(HostName());
11911         gameInfo.date = PGNDate();
11912         gameInfo.round = StrSave("-");
11913         gameInfo.white = StrSave("-");
11914         gameInfo.black = StrSave("-");
11915         break;
11916
11917       case EditPosition:
11918         gameInfo.event = StrSave("Edited position");
11919         gameInfo.site = StrSave(HostName());
11920         gameInfo.date = PGNDate();
11921         gameInfo.round = StrSave("-");
11922         gameInfo.white = StrSave("-");
11923         gameInfo.black = StrSave("-");
11924         break;
11925
11926       case IcsPlayingWhite:
11927       case IcsPlayingBlack:
11928       case IcsObserving:
11929       case IcsExamining:
11930         break;
11931
11932       case PlayFromGameFile:
11933         gameInfo.event = StrSave("Game from non-PGN file");
11934         gameInfo.site = StrSave(HostName());
11935         gameInfo.date = PGNDate();
11936         gameInfo.round = StrSave("-");
11937         gameInfo.white = StrSave("?");
11938         gameInfo.black = StrSave("?");
11939         break;
11940
11941       default:
11942         break;
11943     }
11944 }
11945
11946 void
11947 ReplaceComment(index, text)
11948      int index;
11949      char *text;
11950 {
11951     int len;
11952
11953     while (*text == '\n') text++;
11954     len = strlen(text);
11955     while (len > 0 && text[len - 1] == '\n') len--;
11956
11957     if (commentList[index] != NULL)
11958       free(commentList[index]);
11959
11960     if (len == 0) {
11961         commentList[index] = NULL;
11962         return;
11963     }
11964     commentList[index] = (char *) malloc(len + 2);
11965     strncpy(commentList[index], text, len);
11966     commentList[index][len] = '\n';
11967     commentList[index][len + 1] = NULLCHAR;
11968 }
11969
11970 void
11971 CrushCRs(text)
11972      char *text;
11973 {
11974   char *p = text;
11975   char *q = text;
11976   char ch;
11977
11978   do {
11979     ch = *p++;
11980     if (ch == '\r') continue;
11981     *q++ = ch;
11982   } while (ch != '\0');
11983 }
11984
11985 void
11986 AppendComment(index, text)
11987      int index;
11988      char *text;
11989 {
11990     int oldlen, len;
11991     char *old;
11992
11993     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
11994
11995     CrushCRs(text);
11996     while (*text == '\n') text++;
11997     len = strlen(text);
11998     while (len > 0 && text[len - 1] == '\n') len--;
11999
12000     if (len == 0) return;
12001
12002     if (commentList[index] != NULL) {
12003         old = commentList[index];
12004         oldlen = strlen(old);
12005         commentList[index] = (char *) malloc(oldlen + len + 2);
12006         strcpy(commentList[index], old);
12007         free(old);
12008         strncpy(&commentList[index][oldlen], text, len);
12009         commentList[index][oldlen + len] = '\n';
12010         commentList[index][oldlen + len + 1] = NULLCHAR;
12011     } else {
12012         commentList[index] = (char *) malloc(len + 2);
12013         strncpy(commentList[index], text, len);
12014         commentList[index][len] = '\n';
12015         commentList[index][len + 1] = NULLCHAR;
12016     }
12017 }
12018
12019 static char * FindStr( char * text, char * sub_text )
12020 {
12021     char * result = strstr( text, sub_text );
12022
12023     if( result != NULL ) {
12024         result += strlen( sub_text );
12025     }
12026
12027     return result;
12028 }
12029
12030 /* [AS] Try to extract PV info from PGN comment */
12031 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12032 char *GetInfoFromComment( int index, char * text )
12033 {
12034     char * sep = text;
12035
12036     if( text != NULL && index > 0 ) {
12037         int score = 0;
12038         int depth = 0;
12039         int time = -1, sec = 0, deci;
12040         char * s_eval = FindStr( text, "[%eval " );
12041         char * s_emt = FindStr( text, "[%emt " );
12042
12043         if( s_eval != NULL || s_emt != NULL ) {
12044             /* New style */
12045             char delim;
12046
12047             if( s_eval != NULL ) {
12048                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12049                     return text;
12050                 }
12051
12052                 if( delim != ']' ) {
12053                     return text;
12054                 }
12055             }
12056
12057             if( s_emt != NULL ) {
12058             }
12059         }
12060         else {
12061             /* We expect something like: [+|-]nnn.nn/dd */
12062             int score_lo = 0;
12063
12064             sep = strchr( text, '/' );
12065             if( sep == NULL || sep < (text+4) ) {
12066                 return text;
12067             }
12068
12069             time = -1; sec = -1; deci = -1;
12070             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12071                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12072                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12073                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12074                 return text;
12075             }
12076
12077             if( score_lo < 0 || score_lo >= 100 ) {
12078                 return text;
12079             }
12080
12081             if(sec >= 0) time = 600*time + 10*sec; else
12082             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12083
12084             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12085
12086             /* [HGM] PV time: now locate end of PV info */
12087             while( *++sep >= '0' && *sep <= '9'); // strip depth
12088             if(time >= 0)
12089             while( *++sep >= '0' && *sep <= '9'); // strip time
12090             if(sec >= 0)
12091             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12092             if(deci >= 0)
12093             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12094             while(*sep == ' ') sep++;
12095         }
12096
12097         if( depth <= 0 ) {
12098             return text;
12099         }
12100
12101         if( time < 0 ) {
12102             time = -1;
12103         }
12104
12105         pvInfoList[index-1].depth = depth;
12106         pvInfoList[index-1].score = score;
12107         pvInfoList[index-1].time  = 10*time; // centi-sec
12108     }
12109     return sep;
12110 }
12111
12112 void
12113 SendToProgram(message, cps)
12114      char *message;
12115      ChessProgramState *cps;
12116 {
12117     int count, outCount, error;
12118     char buf[MSG_SIZ];
12119
12120     if (cps->pr == NULL) return;
12121     Attention(cps);
12122     
12123     if (appData.debugMode) {
12124         TimeMark now;
12125         GetTimeMark(&now);
12126         fprintf(debugFP, "%ld >%-6s: %s", 
12127                 SubtractTimeMarks(&now, &programStartTime),
12128                 cps->which, message);
12129     }
12130     
12131     count = strlen(message);
12132     outCount = OutputToProcess(cps->pr, message, count, &error);
12133     if (outCount < count && !exiting 
12134                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12135         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12136         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12137             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12138                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12139                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12140             } else {
12141                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12142             }
12143             gameInfo.resultDetails = buf;
12144         }
12145         DisplayFatalError(buf, error, 1);
12146     }
12147 }
12148
12149 void
12150 ReceiveFromProgram(isr, closure, message, count, error)
12151      InputSourceRef isr;
12152      VOIDSTAR closure;
12153      char *message;
12154      int count;
12155      int error;
12156 {
12157     char *end_str;
12158     char buf[MSG_SIZ];
12159     ChessProgramState *cps = (ChessProgramState *)closure;
12160
12161     if (isr != cps->isr) return; /* Killed intentionally */
12162     if (count <= 0) {
12163         if (count == 0) {
12164             sprintf(buf,
12165                     _("Error: %s chess program (%s) exited unexpectedly"),
12166                     cps->which, cps->program);
12167         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12168                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12169                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12170                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12171                 } else {
12172                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12173                 }
12174                 gameInfo.resultDetails = buf;
12175             }
12176             RemoveInputSource(cps->isr);
12177             DisplayFatalError(buf, 0, 1);
12178         } else {
12179             sprintf(buf,
12180                     _("Error reading from %s chess program (%s)"),
12181                     cps->which, cps->program);
12182             RemoveInputSource(cps->isr);
12183
12184             /* [AS] Program is misbehaving badly... kill it */
12185             if( count == -2 ) {
12186                 DestroyChildProcess( cps->pr, 9 );
12187                 cps->pr = NoProc;
12188             }
12189
12190             DisplayFatalError(buf, error, 1);
12191         }
12192         return;
12193     }
12194     
12195     if ((end_str = strchr(message, '\r')) != NULL)
12196       *end_str = NULLCHAR;
12197     if ((end_str = strchr(message, '\n')) != NULL)
12198       *end_str = NULLCHAR;
12199     
12200     if (appData.debugMode) {
12201         TimeMark now; int print = 1;
12202         char *quote = ""; char c; int i;
12203
12204         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12205                 char start = message[0];
12206                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12207                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12208                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12209                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12210                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12211                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12212                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
12213                         { quote = "# "; print = (appData.engineComments == 2); }
12214                 message[0] = start; // restore original message
12215         }
12216         if(print) {
12217                 GetTimeMark(&now);
12218                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12219                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12220                         quote,
12221                         message);
12222         }
12223     }
12224
12225     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12226     if (appData.icsEngineAnalyze) {
12227         if (strstr(message, "whisper") != NULL ||
12228              strstr(message, "kibitz") != NULL || 
12229             strstr(message, "tellics") != NULL) return;
12230     }
12231
12232     HandleMachineMove(message, cps);
12233 }
12234
12235
12236 void
12237 SendTimeControl(cps, mps, tc, inc, sd, st)
12238      ChessProgramState *cps;
12239      int mps, inc, sd, st;
12240      long tc;
12241 {
12242     char buf[MSG_SIZ];
12243     int seconds;
12244
12245     if( timeControl_2 > 0 ) {
12246         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12247             tc = timeControl_2;
12248         }
12249     }
12250     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12251     inc /= cps->timeOdds;
12252     st  /= cps->timeOdds;
12253
12254     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12255
12256     if (st > 0) {
12257       /* Set exact time per move, normally using st command */
12258       if (cps->stKludge) {
12259         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12260         seconds = st % 60;
12261         if (seconds == 0) {
12262           sprintf(buf, "level 1 %d\n", st/60);
12263         } else {
12264           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12265         }
12266       } else {
12267         sprintf(buf, "st %d\n", st);
12268       }
12269     } else {
12270       /* Set conventional or incremental time control, using level command */
12271       if (seconds == 0) {
12272         /* Note old gnuchess bug -- minutes:seconds used to not work.
12273            Fixed in later versions, but still avoid :seconds
12274            when seconds is 0. */
12275         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12276       } else {
12277         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12278                 seconds, inc/1000);
12279       }
12280     }
12281     SendToProgram(buf, cps);
12282
12283     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12284     /* Orthogonally, limit search to given depth */
12285     if (sd > 0) {
12286       if (cps->sdKludge) {
12287         sprintf(buf, "depth\n%d\n", sd);
12288       } else {
12289         sprintf(buf, "sd %d\n", sd);
12290       }
12291       SendToProgram(buf, cps);
12292     }
12293
12294     if(cps->nps > 0) { /* [HGM] nps */
12295         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12296         else {
12297                 sprintf(buf, "nps %d\n", cps->nps);
12298               SendToProgram(buf, cps);
12299         }
12300     }
12301 }
12302
12303 ChessProgramState *WhitePlayer()
12304 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12305 {
12306     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12307        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12308         return &second;
12309     return &first;
12310 }
12311
12312 void
12313 SendTimeRemaining(cps, machineWhite)
12314      ChessProgramState *cps;
12315      int /*boolean*/ machineWhite;
12316 {
12317     char message[MSG_SIZ];
12318     long time, otime;
12319
12320     /* Note: this routine must be called when the clocks are stopped
12321        or when they have *just* been set or switched; otherwise
12322        it will be off by the time since the current tick started.
12323     */
12324     if (machineWhite) {
12325         time = whiteTimeRemaining / 10;
12326         otime = blackTimeRemaining / 10;
12327     } else {
12328         time = blackTimeRemaining / 10;
12329         otime = whiteTimeRemaining / 10;
12330     }
12331     /* [HGM] translate opponent's time by time-odds factor */
12332     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12333     if (appData.debugMode) {
12334         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12335     }
12336
12337     if (time <= 0) time = 1;
12338     if (otime <= 0) otime = 1;
12339     
12340     sprintf(message, "time %ld\n", time);
12341     SendToProgram(message, cps);
12342
12343     sprintf(message, "otim %ld\n", otime);
12344     SendToProgram(message, cps);
12345 }
12346
12347 int
12348 BoolFeature(p, name, loc, cps)
12349      char **p;
12350      char *name;
12351      int *loc;
12352      ChessProgramState *cps;
12353 {
12354   char buf[MSG_SIZ];
12355   int len = strlen(name);
12356   int val;
12357   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12358     (*p) += len + 1;
12359     sscanf(*p, "%d", &val);
12360     *loc = (val != 0);
12361     while (**p && **p != ' ') (*p)++;
12362     sprintf(buf, "accepted %s\n", name);
12363     SendToProgram(buf, cps);
12364     return TRUE;
12365   }
12366   return FALSE;
12367 }
12368
12369 int
12370 IntFeature(p, name, loc, cps)
12371      char **p;
12372      char *name;
12373      int *loc;
12374      ChessProgramState *cps;
12375 {
12376   char buf[MSG_SIZ];
12377   int len = strlen(name);
12378   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12379     (*p) += len + 1;
12380     sscanf(*p, "%d", loc);
12381     while (**p && **p != ' ') (*p)++;
12382     sprintf(buf, "accepted %s\n", name);
12383     SendToProgram(buf, cps);
12384     return TRUE;
12385   }
12386   return FALSE;
12387 }
12388
12389 int
12390 StringFeature(p, name, loc, cps)
12391      char **p;
12392      char *name;
12393      char loc[];
12394      ChessProgramState *cps;
12395 {
12396   char buf[MSG_SIZ];
12397   int len = strlen(name);
12398   if (strncmp((*p), name, len) == 0
12399       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12400     (*p) += len + 2;
12401     sscanf(*p, "%[^\"]", loc);
12402     while (**p && **p != '\"') (*p)++;
12403     if (**p == '\"') (*p)++;
12404     sprintf(buf, "accepted %s\n", name);
12405     SendToProgram(buf, cps);
12406     return TRUE;
12407   }
12408   return FALSE;
12409 }
12410
12411 int 
12412 ParseOption(Option *opt, ChessProgramState *cps)
12413 // [HGM] options: process the string that defines an engine option, and determine
12414 // name, type, default value, and allowed value range
12415 {
12416         char *p, *q, buf[MSG_SIZ];
12417         int n, min = (-1)<<31, max = 1<<31, def;
12418
12419         if(p = strstr(opt->name, " -spin ")) {
12420             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12421             if(max < min) max = min; // enforce consistency
12422             if(def < min) def = min;
12423             if(def > max) def = max;
12424             opt->value = def;
12425             opt->min = min;
12426             opt->max = max;
12427             opt->type = Spin;
12428         } else if((p = strstr(opt->name, " -slider "))) {
12429             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12430             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12431             if(max < min) max = min; // enforce consistency
12432             if(def < min) def = min;
12433             if(def > max) def = max;
12434             opt->value = def;
12435             opt->min = min;
12436             opt->max = max;
12437             opt->type = Spin; // Slider;
12438         } else if((p = strstr(opt->name, " -string "))) {
12439             opt->textValue = p+9;
12440             opt->type = TextBox;
12441         } else if((p = strstr(opt->name, " -file "))) {
12442             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12443             opt->textValue = p+7;
12444             opt->type = TextBox; // FileName;
12445         } else if((p = strstr(opt->name, " -path "))) {
12446             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12447             opt->textValue = p+7;
12448             opt->type = TextBox; // PathName;
12449         } else if(p = strstr(opt->name, " -check ")) {
12450             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12451             opt->value = (def != 0);
12452             opt->type = CheckBox;
12453         } else if(p = strstr(opt->name, " -combo ")) {
12454             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12455             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12456             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12457             opt->value = n = 0;
12458             while(q = StrStr(q, " /// ")) {
12459                 n++; *q = 0;    // count choices, and null-terminate each of them
12460                 q += 5;
12461                 if(*q == '*') { // remember default, which is marked with * prefix
12462                     q++;
12463                     opt->value = n;
12464                 }
12465                 cps->comboList[cps->comboCnt++] = q;
12466             }
12467             cps->comboList[cps->comboCnt++] = NULL;
12468             opt->max = n + 1;
12469             opt->type = ComboBox;
12470         } else if(p = strstr(opt->name, " -button")) {
12471             opt->type = Button;
12472         } else if(p = strstr(opt->name, " -save")) {
12473             opt->type = SaveButton;
12474         } else return FALSE;
12475         *p = 0; // terminate option name
12476         // now look if the command-line options define a setting for this engine option.
12477         if(cps->optionSettings && cps->optionSettings[0])
12478             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12479         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12480                 sprintf(buf, "option %s", p);
12481                 if(p = strstr(buf, ",")) *p = 0;
12482                 strcat(buf, "\n");
12483                 SendToProgram(buf, cps);
12484         }
12485         return TRUE;
12486 }
12487
12488 void
12489 FeatureDone(cps, val)
12490      ChessProgramState* cps;
12491      int val;
12492 {
12493   DelayedEventCallback cb = GetDelayedEvent();
12494   if ((cb == InitBackEnd3 && cps == &first) ||
12495       (cb == TwoMachinesEventIfReady && cps == &second)) {
12496     CancelDelayedEvent();
12497     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12498   }
12499   cps->initDone = val;
12500 }
12501
12502 /* Parse feature command from engine */
12503 void
12504 ParseFeatures(args, cps)
12505      char* args;
12506      ChessProgramState *cps;  
12507 {
12508   char *p = args;
12509   char *q;
12510   int val;
12511   char buf[MSG_SIZ];
12512
12513   for (;;) {
12514     while (*p == ' ') p++;
12515     if (*p == NULLCHAR) return;
12516
12517     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12518     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12519     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12520     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12521     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12522     if (BoolFeature(&p, "reuse", &val, cps)) {
12523       /* Engine can disable reuse, but can't enable it if user said no */
12524       if (!val) cps->reuse = FALSE;
12525       continue;
12526     }
12527     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12528     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12529       if (gameMode == TwoMachinesPlay) {
12530         DisplayTwoMachinesTitle();
12531       } else {
12532         DisplayTitle("");
12533       }
12534       continue;
12535     }
12536     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12537     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12538     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12539     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12540     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12541     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12542     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12543     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12544     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12545     if (IntFeature(&p, "done", &val, cps)) {
12546       FeatureDone(cps, val);
12547       continue;
12548     }
12549     /* Added by Tord: */
12550     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12551     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12552     /* End of additions by Tord */
12553
12554     /* [HGM] added features: */
12555     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12556     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12557     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12558     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12559     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12560     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12561     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12562         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12563             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12564             SendToProgram(buf, cps);
12565             continue;
12566         }
12567         if(cps->nrOptions >= MAX_OPTIONS) {
12568             cps->nrOptions--;
12569             sprintf(buf, "%s engine has too many options\n", cps->which);
12570             DisplayError(buf, 0);
12571         }
12572         continue;
12573     }
12574     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12575     /* End of additions by HGM */
12576
12577     /* unknown feature: complain and skip */
12578     q = p;
12579     while (*q && *q != '=') q++;
12580     sprintf(buf, "rejected %.*s\n", q-p, p);
12581     SendToProgram(buf, cps);
12582     p = q;
12583     if (*p == '=') {
12584       p++;
12585       if (*p == '\"') {
12586         p++;
12587         while (*p && *p != '\"') p++;
12588         if (*p == '\"') p++;
12589       } else {
12590         while (*p && *p != ' ') p++;
12591       }
12592     }
12593   }
12594
12595 }
12596
12597 void
12598 PeriodicUpdatesEvent(newState)
12599      int newState;
12600 {
12601     if (newState == appData.periodicUpdates)
12602       return;
12603
12604     appData.periodicUpdates=newState;
12605
12606     /* Display type changes, so update it now */
12607     DisplayAnalysis();
12608
12609     /* Get the ball rolling again... */
12610     if (newState) {
12611         AnalysisPeriodicEvent(1);
12612         StartAnalysisClock();
12613     }
12614 }
12615
12616 void
12617 PonderNextMoveEvent(newState)
12618      int newState;
12619 {
12620     if (newState == appData.ponderNextMove) return;
12621     if (gameMode == EditPosition) EditPositionDone();
12622     if (newState) {
12623         SendToProgram("hard\n", &first);
12624         if (gameMode == TwoMachinesPlay) {
12625             SendToProgram("hard\n", &second);
12626         }
12627     } else {
12628         SendToProgram("easy\n", &first);
12629         thinkOutput[0] = NULLCHAR;
12630         if (gameMode == TwoMachinesPlay) {
12631             SendToProgram("easy\n", &second);
12632         }
12633     }
12634     appData.ponderNextMove = newState;
12635 }
12636
12637 void
12638 NewSettingEvent(option, command, value)
12639      char *command;
12640      int option, value;
12641 {
12642     char buf[MSG_SIZ];
12643
12644     if (gameMode == EditPosition) EditPositionDone();
12645     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12646     SendToProgram(buf, &first);
12647     if (gameMode == TwoMachinesPlay) {
12648         SendToProgram(buf, &second);
12649     }
12650 }
12651
12652 void
12653 ShowThinkingEvent()
12654 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12655 {
12656     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12657     int newState = appData.showThinking
12658         // [HGM] thinking: other features now need thinking output as well
12659         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12660     
12661     if (oldState == newState) return;
12662     oldState = newState;
12663     if (gameMode == EditPosition) EditPositionDone();
12664     if (oldState) {
12665         SendToProgram("post\n", &first);
12666         if (gameMode == TwoMachinesPlay) {
12667             SendToProgram("post\n", &second);
12668         }
12669     } else {
12670         SendToProgram("nopost\n", &first);
12671         thinkOutput[0] = NULLCHAR;
12672         if (gameMode == TwoMachinesPlay) {
12673             SendToProgram("nopost\n", &second);
12674         }
12675     }
12676 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12677 }
12678
12679 void
12680 AskQuestionEvent(title, question, replyPrefix, which)
12681      char *title; char *question; char *replyPrefix; char *which;
12682 {
12683   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12684   if (pr == NoProc) return;
12685   AskQuestion(title, question, replyPrefix, pr);
12686 }
12687
12688 void
12689 DisplayMove(moveNumber)
12690      int moveNumber;
12691 {
12692     char message[MSG_SIZ];
12693     char res[MSG_SIZ];
12694     char cpThinkOutput[MSG_SIZ];
12695
12696     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12697     
12698     if (moveNumber == forwardMostMove - 1 || 
12699         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12700
12701         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12702
12703         if (strchr(cpThinkOutput, '\n')) {
12704             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12705         }
12706     } else {
12707         *cpThinkOutput = NULLCHAR;
12708     }
12709
12710     /* [AS] Hide thinking from human user */
12711     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12712         *cpThinkOutput = NULLCHAR;
12713         if( thinkOutput[0] != NULLCHAR ) {
12714             int i;
12715
12716             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12717                 cpThinkOutput[i] = '.';
12718             }
12719             cpThinkOutput[i] = NULLCHAR;
12720             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12721         }
12722     }
12723
12724     if (moveNumber == forwardMostMove - 1 &&
12725         gameInfo.resultDetails != NULL) {
12726         if (gameInfo.resultDetails[0] == NULLCHAR) {
12727             sprintf(res, " %s", PGNResult(gameInfo.result));
12728         } else {
12729             sprintf(res, " {%s} %s",
12730                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12731         }
12732     } else {
12733         res[0] = NULLCHAR;
12734     }
12735
12736     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12737         DisplayMessage(res, cpThinkOutput);
12738     } else {
12739         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12740                 WhiteOnMove(moveNumber) ? " " : ".. ",
12741                 parseList[moveNumber], res);
12742         DisplayMessage(message, cpThinkOutput);
12743     }
12744 }
12745
12746 void
12747 DisplayAnalysisText(text)
12748      char *text;
12749 {
12750     char buf[MSG_SIZ];
12751
12752     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile 
12753                || appData.icsEngineAnalyze) {
12754         sprintf(buf, "Analysis (%s)", first.tidy);
12755         AnalysisPopUp(buf, text);
12756     }
12757 }
12758
12759 static int
12760 only_one_move(str)
12761      char *str;
12762 {
12763     while (*str && isspace(*str)) ++str;
12764     while (*str && !isspace(*str)) ++str;
12765     if (!*str) return 1;
12766     while (*str && isspace(*str)) ++str;
12767     if (!*str) return 1;
12768     return 0;
12769 }
12770
12771 void
12772 DisplayAnalysis()
12773 {
12774     char buf[MSG_SIZ];
12775     char lst[MSG_SIZ / 2];
12776     double nps;
12777     static char *xtra[] = { "", " (--)", " (++)" };
12778     int h, m, s, cs;
12779   
12780     if (programStats.time == 0) {
12781         programStats.time = 1;
12782     }
12783   
12784     if (programStats.got_only_move) {
12785         safeStrCpy(buf, programStats.movelist, sizeof(buf));
12786     } else {
12787         safeStrCpy( lst, programStats.movelist, sizeof(lst));
12788
12789         nps = (u64ToDouble(programStats.nodes) /
12790              ((double)programStats.time /100.0));
12791
12792         cs = programStats.time % 100;
12793         s = programStats.time / 100;
12794         h = (s / (60*60));
12795         s = s - h*60*60;
12796         m = (s/60);
12797         s = s - m*60;
12798
12799         if (programStats.moves_left > 0 && appData.periodicUpdates) {
12800           if (programStats.move_name[0] != NULLCHAR) {
12801             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12802                     programStats.depth,
12803                     programStats.nr_moves-programStats.moves_left,
12804                     programStats.nr_moves, programStats.move_name,
12805                     ((float)programStats.score)/100.0, lst,
12806                     only_one_move(lst)?
12807                     xtra[programStats.got_fail] : "",
12808                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12809           } else {
12810             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12811                     programStats.depth,
12812                     programStats.nr_moves-programStats.moves_left,
12813                     programStats.nr_moves, ((float)programStats.score)/100.0,
12814                     lst,
12815                     only_one_move(lst)?
12816                     xtra[programStats.got_fail] : "",
12817                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12818           }
12819         } else {
12820             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12821                     programStats.depth,
12822                     ((float)programStats.score)/100.0,
12823                     lst,
12824                     only_one_move(lst)?
12825                     xtra[programStats.got_fail] : "",
12826                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12827         }
12828     }
12829     DisplayAnalysisText(buf);
12830 }
12831
12832 void
12833 DisplayComment(moveNumber, text)
12834      int moveNumber;
12835      char *text;
12836 {
12837     char title[MSG_SIZ];
12838     char buf[8000]; // comment can be long!
12839     int score, depth;
12840
12841     if( appData.autoDisplayComment ) {
12842         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12843             strcpy(title, "Comment");
12844         } else {
12845             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12846                     WhiteOnMove(moveNumber) ? " " : ".. ",
12847                     parseList[moveNumber]);
12848         }
12849         // [HGM] PV info: display PV info together with (or as) comment
12850         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12851             if(text == NULL) text = "";                                           
12852             score = pvInfoList[moveNumber].score;
12853             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12854                               depth, (pvInfoList[moveNumber].time+50)/100, text);
12855             text = buf;
12856         }
12857     } else title[0] = 0;
12858
12859     if (text != NULL)
12860         CommentPopUp(title, text);
12861 }
12862
12863 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12864  * might be busy thinking or pondering.  It can be omitted if your
12865  * gnuchess is configured to stop thinking immediately on any user
12866  * input.  However, that gnuchess feature depends on the FIONREAD
12867  * ioctl, which does not work properly on some flavors of Unix.
12868  */
12869 void
12870 Attention(cps)
12871      ChessProgramState *cps;
12872 {
12873 #if ATTENTION
12874     if (!cps->useSigint) return;
12875     if (appData.noChessProgram || (cps->pr == NoProc)) return;
12876     switch (gameMode) {
12877       case MachinePlaysWhite:
12878       case MachinePlaysBlack:
12879       case TwoMachinesPlay:
12880       case IcsPlayingWhite:
12881       case IcsPlayingBlack:
12882       case AnalyzeMode:
12883       case AnalyzeFile:
12884         /* Skip if we know it isn't thinking */
12885         if (!cps->maybeThinking) return;
12886         if (appData.debugMode)
12887           fprintf(debugFP, "Interrupting %s\n", cps->which);
12888         InterruptChildProcess(cps->pr);
12889         cps->maybeThinking = FALSE;
12890         break;
12891       default:
12892         break;
12893     }
12894 #endif /*ATTENTION*/
12895 }
12896
12897 int
12898 CheckFlags()
12899 {
12900     if (whiteTimeRemaining <= 0) {
12901         if (!whiteFlag) {
12902             whiteFlag = TRUE;
12903             if (appData.icsActive) {
12904                 if (appData.autoCallFlag &&
12905                     gameMode == IcsPlayingBlack && !blackFlag) {
12906                   SendToICS(ics_prefix);
12907                   SendToICS("flag\n");
12908                 }
12909             } else {
12910                 if (blackFlag) {
12911                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12912                 } else {
12913                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12914                     if (appData.autoCallFlag) {
12915                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12916                         return TRUE;
12917                     }
12918                 }
12919             }
12920         }
12921     }
12922     if (blackTimeRemaining <= 0) {
12923         if (!blackFlag) {
12924             blackFlag = TRUE;
12925             if (appData.icsActive) {
12926                 if (appData.autoCallFlag &&
12927                     gameMode == IcsPlayingWhite && !whiteFlag) {
12928                   SendToICS(ics_prefix);
12929                   SendToICS("flag\n");
12930                 }
12931             } else {
12932                 if (whiteFlag) {
12933                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12934                 } else {
12935                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12936                     if (appData.autoCallFlag) {
12937                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12938                         return TRUE;
12939                     }
12940                 }
12941             }
12942         }
12943     }
12944     return FALSE;
12945 }
12946
12947 void
12948 CheckTimeControl()
12949 {
12950     if (!appData.clockMode || appData.icsActive ||
12951         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12952
12953     /*
12954      * add time to clocks when time control is achieved ([HGM] now also used for increment)
12955      */
12956     if ( !WhiteOnMove(forwardMostMove) )
12957         /* White made time control */
12958         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12959         /* [HGM] time odds: correct new time quota for time odds! */
12960                                             / WhitePlayer()->timeOdds;
12961       else
12962         /* Black made time control */
12963         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12964                                             / WhitePlayer()->other->timeOdds;
12965 }
12966
12967 void
12968 DisplayBothClocks()
12969 {
12970     int wom = gameMode == EditPosition ?
12971       !blackPlaysFirst : WhiteOnMove(currentMove);
12972     DisplayWhiteClock(whiteTimeRemaining, wom);
12973     DisplayBlackClock(blackTimeRemaining, !wom);
12974 }
12975
12976
12977 /* Timekeeping seems to be a portability nightmare.  I think everyone
12978    has ftime(), but I'm really not sure, so I'm including some ifdefs
12979    to use other calls if you don't.  Clocks will be less accurate if
12980    you have neither ftime nor gettimeofday.
12981 */
12982
12983 /* VS 2008 requires the #include outside of the function */
12984 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
12985 #include <sys/timeb.h>
12986 #endif
12987
12988 /* Get the current time as a TimeMark */
12989 void
12990 GetTimeMark(tm)
12991      TimeMark *tm;
12992 {
12993 #if HAVE_GETTIMEOFDAY
12994
12995     struct timeval timeVal;
12996     struct timezone timeZone;
12997
12998     gettimeofday(&timeVal, &timeZone);
12999     tm->sec = (long) timeVal.tv_sec; 
13000     tm->ms = (int) (timeVal.tv_usec / 1000L);
13001
13002 #else /*!HAVE_GETTIMEOFDAY*/
13003 #if HAVE_FTIME
13004
13005 // include <sys/timeb.h> / moved to just above start of function
13006     struct timeb timeB;
13007
13008     ftime(&timeB);
13009     tm->sec = (long) timeB.time;
13010     tm->ms = (int) timeB.millitm;
13011
13012 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13013     tm->sec = (long) time(NULL);
13014     tm->ms = 0;
13015 #endif
13016 #endif
13017 }
13018
13019 /* Return the difference in milliseconds between two
13020    time marks.  We assume the difference will fit in a long!
13021 */
13022 long
13023 SubtractTimeMarks(tm2, tm1)
13024      TimeMark *tm2, *tm1;
13025 {
13026     return 1000L*(tm2->sec - tm1->sec) +
13027            (long) (tm2->ms - tm1->ms);
13028 }
13029
13030
13031 /*
13032  * Code to manage the game clocks.
13033  *
13034  * In tournament play, black starts the clock and then white makes a move.
13035  * We give the human user a slight advantage if he is playing white---the
13036  * clocks don't run until he makes his first move, so it takes zero time.
13037  * Also, we don't account for network lag, so we could get out of sync
13038  * with GNU Chess's clock -- but then, referees are always right.  
13039  */
13040
13041 static TimeMark tickStartTM;
13042 static long intendedTickLength;
13043
13044 long
13045 NextTickLength(timeRemaining)
13046      long timeRemaining;
13047 {
13048     long nominalTickLength, nextTickLength;
13049
13050     if (timeRemaining > 0L && timeRemaining <= 10000L)
13051       nominalTickLength = 100L;
13052     else
13053       nominalTickLength = 1000L;
13054     nextTickLength = timeRemaining % nominalTickLength;
13055     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13056
13057     return nextTickLength;
13058 }
13059
13060 /* Adjust clock one minute up or down */
13061 void
13062 AdjustClock(Boolean which, int dir)
13063 {
13064     if(which) blackTimeRemaining += 60000*dir;
13065     else      whiteTimeRemaining += 60000*dir;
13066     DisplayBothClocks();
13067 }
13068
13069 /* Stop clocks and reset to a fresh time control */
13070 void
13071 ResetClocks() 
13072 {
13073     (void) StopClockTimer();
13074     if (appData.icsActive) {
13075         whiteTimeRemaining = blackTimeRemaining = 0;
13076     } else { /* [HGM] correct new time quote for time odds */
13077         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13078         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13079     }
13080     if (whiteFlag || blackFlag) {
13081         DisplayTitle("");
13082         whiteFlag = blackFlag = FALSE;
13083     }
13084     DisplayBothClocks();
13085 }
13086
13087 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13088
13089 /* Decrement running clock by amount of time that has passed */
13090 void
13091 DecrementClocks()
13092 {
13093     long timeRemaining;
13094     long lastTickLength, fudge;
13095     TimeMark now;
13096
13097     if (!appData.clockMode) return;
13098     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13099         
13100     GetTimeMark(&now);
13101
13102     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13103
13104     /* Fudge if we woke up a little too soon */
13105     fudge = intendedTickLength - lastTickLength;
13106     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13107
13108     if (WhiteOnMove(forwardMostMove)) {
13109         if(whiteNPS >= 0) lastTickLength = 0;
13110         timeRemaining = whiteTimeRemaining -= lastTickLength;
13111         DisplayWhiteClock(whiteTimeRemaining - fudge,
13112                           WhiteOnMove(currentMove));
13113     } else {
13114         if(blackNPS >= 0) lastTickLength = 0;
13115         timeRemaining = blackTimeRemaining -= lastTickLength;
13116         DisplayBlackClock(blackTimeRemaining - fudge,
13117                           !WhiteOnMove(currentMove));
13118     }
13119
13120     if (CheckFlags()) return;
13121         
13122     tickStartTM = now;
13123     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13124     StartClockTimer(intendedTickLength);
13125
13126     /* if the time remaining has fallen below the alarm threshold, sound the
13127      * alarm. if the alarm has sounded and (due to a takeback or time control
13128      * with increment) the time remaining has increased to a level above the
13129      * threshold, reset the alarm so it can sound again. 
13130      */
13131     
13132     if (appData.icsActive && appData.icsAlarm) {
13133
13134         /* make sure we are dealing with the user's clock */
13135         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13136                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13137            )) return;
13138
13139         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13140             alarmSounded = FALSE;
13141         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13142             PlayAlarmSound();
13143             alarmSounded = TRUE;
13144         }
13145     }
13146 }
13147
13148
13149 /* A player has just moved, so stop the previously running
13150    clock and (if in clock mode) start the other one.
13151    We redisplay both clocks in case we're in ICS mode, because
13152    ICS gives us an update to both clocks after every move.
13153    Note that this routine is called *after* forwardMostMove
13154    is updated, so the last fractional tick must be subtracted
13155    from the color that is *not* on move now.
13156 */
13157 void
13158 SwitchClocks()
13159 {
13160     long lastTickLength;
13161     TimeMark now;
13162     int flagged = FALSE;
13163
13164     GetTimeMark(&now);
13165
13166     if (StopClockTimer() && appData.clockMode) {
13167         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13168         if (WhiteOnMove(forwardMostMove)) {
13169             if(blackNPS >= 0) lastTickLength = 0;
13170             blackTimeRemaining -= lastTickLength;
13171            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13172 //         if(pvInfoList[forwardMostMove-1].time == -1)
13173                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13174                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13175         } else {
13176            if(whiteNPS >= 0) lastTickLength = 0;
13177            whiteTimeRemaining -= lastTickLength;
13178            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13179 //         if(pvInfoList[forwardMostMove-1].time == -1)
13180                  pvInfoList[forwardMostMove-1].time = 
13181                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13182         }
13183         flagged = CheckFlags();
13184     }
13185     CheckTimeControl();
13186
13187     if (flagged || !appData.clockMode) return;
13188
13189     switch (gameMode) {
13190       case MachinePlaysBlack:
13191       case MachinePlaysWhite:
13192       case BeginningOfGame:
13193         if (pausing) return;
13194         break;
13195
13196       case EditGame:
13197       case PlayFromGameFile:
13198       case IcsExamining:
13199         return;
13200
13201       default:
13202         break;
13203     }
13204
13205     tickStartTM = now;
13206     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13207       whiteTimeRemaining : blackTimeRemaining);
13208     StartClockTimer(intendedTickLength);
13209 }
13210         
13211
13212 /* Stop both clocks */
13213 void
13214 StopClocks()
13215 {       
13216     long lastTickLength;
13217     TimeMark now;
13218
13219     if (!StopClockTimer()) return;
13220     if (!appData.clockMode) return;
13221
13222     GetTimeMark(&now);
13223
13224     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13225     if (WhiteOnMove(forwardMostMove)) {
13226         if(whiteNPS >= 0) lastTickLength = 0;
13227         whiteTimeRemaining -= lastTickLength;
13228         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13229     } else {
13230         if(blackNPS >= 0) lastTickLength = 0;
13231         blackTimeRemaining -= lastTickLength;
13232         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13233     }
13234     CheckFlags();
13235 }
13236         
13237 /* Start clock of player on move.  Time may have been reset, so
13238    if clock is already running, stop and restart it. */
13239 void
13240 StartClocks()
13241 {
13242     (void) StopClockTimer(); /* in case it was running already */
13243     DisplayBothClocks();
13244     if (CheckFlags()) return;
13245
13246     if (!appData.clockMode) return;
13247     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13248
13249     GetTimeMark(&tickStartTM);
13250     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13251       whiteTimeRemaining : blackTimeRemaining);
13252
13253    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13254     whiteNPS = blackNPS = -1; 
13255     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13256        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13257         whiteNPS = first.nps;
13258     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13259        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13260         blackNPS = first.nps;
13261     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13262         whiteNPS = second.nps;
13263     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13264         blackNPS = second.nps;
13265     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13266
13267     StartClockTimer(intendedTickLength);
13268 }
13269
13270 char *
13271 TimeString(ms)
13272      long ms;
13273 {
13274     long second, minute, hour, day;
13275     char *sign = "";
13276     static char buf[32];
13277     
13278     if (ms > 0 && ms <= 9900) {
13279       /* convert milliseconds to tenths, rounding up */
13280       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13281
13282       sprintf(buf, " %03.1f ", tenths/10.0);
13283       return buf;
13284     }
13285
13286     /* convert milliseconds to seconds, rounding up */
13287     /* use floating point to avoid strangeness of integer division
13288        with negative dividends on many machines */
13289     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13290
13291     if (second < 0) {
13292         sign = "-";
13293         second = -second;
13294     }
13295     
13296     day = second / (60 * 60 * 24);
13297     second = second % (60 * 60 * 24);
13298     hour = second / (60 * 60);
13299     second = second % (60 * 60);
13300     minute = second / 60;
13301     second = second % 60;
13302     
13303     if (day > 0)
13304       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13305               sign, day, hour, minute, second);
13306     else if (hour > 0)
13307       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13308     else
13309       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13310     
13311     return buf;
13312 }
13313
13314
13315 /*
13316  * This is necessary because some C libraries aren't ANSI C compliant yet.
13317  */
13318 char *
13319 StrStr(string, match)
13320      char *string, *match;
13321 {
13322     int i, length;
13323     
13324     length = strlen(match);
13325     
13326     for (i = strlen(string) - length; i >= 0; i--, string++)
13327       if (!strncmp(match, string, length))
13328         return string;
13329     
13330     return NULL;
13331 }
13332
13333 char *
13334 StrCaseStr(string, match)
13335      char *string, *match;
13336 {
13337     int i, j, length;
13338     
13339     length = strlen(match);
13340     
13341     for (i = strlen(string) - length; i >= 0; i--, string++) {
13342         for (j = 0; j < length; j++) {
13343             if (ToLower(match[j]) != ToLower(string[j]))
13344               break;
13345         }
13346         if (j == length) return string;
13347     }
13348
13349     return NULL;
13350 }
13351
13352 #ifndef _amigados
13353 int
13354 StrCaseCmp(s1, s2)
13355      char *s1, *s2;
13356 {
13357     char c1, c2;
13358     
13359     for (;;) {
13360         c1 = ToLower(*s1++);
13361         c2 = ToLower(*s2++);
13362         if (c1 > c2) return 1;
13363         if (c1 < c2) return -1;
13364         if (c1 == NULLCHAR) return 0;
13365     }
13366 }
13367
13368
13369 int
13370 ToLower(c)
13371      int c;
13372 {
13373     return isupper(c) ? tolower(c) : c;
13374 }
13375
13376
13377 int
13378 ToUpper(c)
13379      int c;
13380 {
13381     return islower(c) ? toupper(c) : c;
13382 }
13383 #endif /* !_amigados    */
13384
13385 char *
13386 StrSave(s)
13387      char *s;
13388 {
13389     char *ret;
13390
13391     if ((ret = (char *) malloc(strlen(s) + 1))) {
13392         strcpy(ret, s);
13393     }
13394     return ret;
13395 }
13396
13397 char *
13398 StrSavePtr(s, savePtr)
13399      char *s, **savePtr;
13400 {
13401     if (*savePtr) {
13402         free(*savePtr);
13403     }
13404     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13405         strcpy(*savePtr, s);
13406     }
13407     return(*savePtr);
13408 }
13409
13410 char *
13411 PGNDate()
13412 {
13413     time_t clock;
13414     struct tm *tm;
13415     char buf[MSG_SIZ];
13416
13417     clock = time((time_t *)NULL);
13418     tm = localtime(&clock);
13419     sprintf(buf, "%04d.%02d.%02d",
13420             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13421     return StrSave(buf);
13422 }
13423
13424
13425 char *
13426 PositionToFEN(move, overrideCastling)
13427      int move;
13428      char *overrideCastling;
13429 {
13430     int i, j, fromX, fromY, toX, toY;
13431     int whiteToPlay;
13432     char buf[128];
13433     char *p, *q;
13434     int emptycount;
13435     ChessSquare piece;
13436
13437     whiteToPlay = (gameMode == EditPosition) ?
13438       !blackPlaysFirst : (move % 2 == 0);
13439     p = buf;
13440
13441     /* Piece placement data */
13442     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13443         emptycount = 0;
13444         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13445             if (boards[move][i][j] == EmptySquare) {
13446                 emptycount++;
13447             } else { ChessSquare piece = boards[move][i][j];
13448                 if (emptycount > 0) {
13449                     if(emptycount<10) /* [HGM] can be >= 10 */
13450                         *p++ = '0' + emptycount;
13451                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13452                     emptycount = 0;
13453                 }
13454                 if(PieceToChar(piece) == '+') {
13455                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13456                     *p++ = '+';
13457                     piece = (ChessSquare)(DEMOTED piece);
13458                 } 
13459                 *p++ = PieceToChar(piece);
13460                 if(p[-1] == '~') {
13461                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13462                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13463                     *p++ = '~';
13464                 }
13465             }
13466         }
13467         if (emptycount > 0) {
13468             if(emptycount<10) /* [HGM] can be >= 10 */
13469                 *p++ = '0' + emptycount;
13470             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13471             emptycount = 0;
13472         }
13473         *p++ = '/';
13474     }
13475     *(p - 1) = ' ';
13476
13477     /* [HGM] print Crazyhouse or Shogi holdings */
13478     if( gameInfo.holdingsWidth ) {
13479         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13480         q = p;
13481         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13482             piece = boards[move][i][BOARD_WIDTH-1];
13483             if( piece != EmptySquare )
13484               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13485                   *p++ = PieceToChar(piece);
13486         }
13487         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13488             piece = boards[move][BOARD_HEIGHT-i-1][0];
13489             if( piece != EmptySquare )
13490               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13491                   *p++ = PieceToChar(piece);
13492         }
13493
13494         if( q == p ) *p++ = '-';
13495         *p++ = ']';
13496         *p++ = ' ';
13497     }
13498
13499     /* Active color */
13500     *p++ = whiteToPlay ? 'w' : 'b';
13501     *p++ = ' ';
13502
13503   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13504     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13505   } else {
13506   if(nrCastlingRights) {
13507      q = p;
13508      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13509        /* [HGM] write directly from rights */
13510            if(castlingRights[move][2] >= 0 &&
13511               castlingRights[move][0] >= 0   )
13512                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13513            if(castlingRights[move][2] >= 0 &&
13514               castlingRights[move][1] >= 0   )
13515                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13516            if(castlingRights[move][5] >= 0 &&
13517               castlingRights[move][3] >= 0   )
13518                 *p++ = castlingRights[move][3] + AAA;
13519            if(castlingRights[move][5] >= 0 &&
13520               castlingRights[move][4] >= 0   )
13521                 *p++ = castlingRights[move][4] + AAA;
13522      } else {
13523
13524         /* [HGM] write true castling rights */
13525         if( nrCastlingRights == 6 ) {
13526             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13527                castlingRights[move][2] >= 0  ) *p++ = 'K';
13528             if(castlingRights[move][1] == BOARD_LEFT &&
13529                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13530             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13531                castlingRights[move][5] >= 0  ) *p++ = 'k';
13532             if(castlingRights[move][4] == BOARD_LEFT &&
13533                castlingRights[move][5] >= 0  ) *p++ = 'q';
13534         }
13535      }
13536      if (q == p) *p++ = '-'; /* No castling rights */
13537      *p++ = ' ';
13538   }
13539
13540   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13541      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13542     /* En passant target square */
13543     if (move > backwardMostMove) {
13544         fromX = moveList[move - 1][0] - AAA;
13545         fromY = moveList[move - 1][1] - ONE;
13546         toX = moveList[move - 1][2] - AAA;
13547         toY = moveList[move - 1][3] - ONE;
13548         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13549             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13550             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13551             fromX == toX) {
13552             /* 2-square pawn move just happened */
13553             *p++ = toX + AAA;
13554             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13555         } else {
13556             *p++ = '-';
13557         }
13558     } else {
13559         *p++ = '-';
13560     }
13561     *p++ = ' ';
13562   }
13563   }
13564
13565     /* [HGM] find reversible plies */
13566     {   int i = 0, j=move;
13567
13568         if (appData.debugMode) { int k;
13569             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13570             for(k=backwardMostMove; k<=forwardMostMove; k++)
13571                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13572
13573         }
13574
13575         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13576         if( j == backwardMostMove ) i += initialRulePlies;
13577         sprintf(p, "%d ", i);
13578         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13579     }
13580     /* Fullmove number */
13581     sprintf(p, "%d", (move / 2) + 1);
13582     
13583     return StrSave(buf);
13584 }
13585
13586 Boolean
13587 ParseFEN(board, blackPlaysFirst, fen)
13588     Board board;
13589      int *blackPlaysFirst;
13590      char *fen;
13591 {
13592     int i, j;
13593     char *p;
13594     int emptycount;
13595     ChessSquare piece;
13596
13597     p = fen;
13598
13599     /* [HGM] by default clear Crazyhouse holdings, if present */
13600     if(gameInfo.holdingsWidth) {
13601        for(i=0; i<BOARD_HEIGHT; i++) {
13602            board[i][0]             = EmptySquare; /* black holdings */
13603            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13604            board[i][1]             = (ChessSquare) 0; /* black counts */
13605            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13606        }
13607     }
13608
13609     /* Piece placement data */
13610     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13611         j = 0;
13612         for (;;) {
13613             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13614                 if (*p == '/') p++;
13615                 emptycount = gameInfo.boardWidth - j;
13616                 while (emptycount--)
13617                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13618                 break;
13619 #if(BOARD_SIZE >= 10)
13620             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13621                 p++; emptycount=10;
13622                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13623                 while (emptycount--)
13624                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13625 #endif
13626             } else if (isdigit(*p)) {
13627                 emptycount = *p++ - '0';
13628                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13629                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13630                 while (emptycount--)
13631                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13632             } else if (*p == '+' || isalpha(*p)) {
13633                 if (j >= gameInfo.boardWidth) return FALSE;
13634                 if(*p=='+') {
13635                     piece = CharToPiece(*++p);
13636                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13637                     piece = (ChessSquare) (PROMOTED piece ); p++;
13638                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13639                 } else piece = CharToPiece(*p++);
13640
13641                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13642                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13643                     piece = (ChessSquare) (PROMOTED piece);
13644                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13645                     p++;
13646                 }
13647                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13648             } else {
13649                 return FALSE;
13650             }
13651         }
13652     }
13653     while (*p == '/' || *p == ' ') p++;
13654
13655     /* [HGM] look for Crazyhouse holdings here */
13656     while(*p==' ') p++;
13657     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13658         if(*p == '[') p++;
13659         if(*p == '-' ) *p++; /* empty holdings */ else {
13660             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13661             /* if we would allow FEN reading to set board size, we would   */
13662             /* have to add holdings and shift the board read so far here   */
13663             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13664                 *p++;
13665                 if((int) piece >= (int) BlackPawn ) {
13666                     i = (int)piece - (int)BlackPawn;
13667                     i = PieceToNumber((ChessSquare)i);
13668                     if( i >= gameInfo.holdingsSize ) return FALSE;
13669                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13670                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13671                 } else {
13672                     i = (int)piece - (int)WhitePawn;
13673                     i = PieceToNumber((ChessSquare)i);
13674                     if( i >= gameInfo.holdingsSize ) return FALSE;
13675                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13676                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13677                 }
13678             }
13679         }
13680         if(*p == ']') *p++;
13681     }
13682
13683     while(*p == ' ') p++;
13684
13685     /* Active color */
13686     switch (*p++) {
13687       case 'w':
13688         *blackPlaysFirst = FALSE;
13689         break;
13690       case 'b': 
13691         *blackPlaysFirst = TRUE;
13692         break;
13693       default:
13694         return FALSE;
13695     }
13696
13697     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13698     /* return the extra info in global variiables             */
13699
13700     /* set defaults in case FEN is incomplete */
13701     FENepStatus = EP_UNKNOWN;
13702     for(i=0; i<nrCastlingRights; i++ ) {
13703         FENcastlingRights[i] =
13704             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13705     }   /* assume possible unless obviously impossible */
13706     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13707     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13708     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13709     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13710     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13711     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13712     FENrulePlies = 0;
13713
13714     while(*p==' ') p++;
13715     if(nrCastlingRights) {
13716       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13717           /* castling indicator present, so default becomes no castlings */
13718           for(i=0; i<nrCastlingRights; i++ ) {
13719                  FENcastlingRights[i] = -1;
13720           }
13721       }
13722       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13723              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13724              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13725              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13726         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13727
13728         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13729             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13730             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13731         }
13732         switch(c) {
13733           case'K':
13734               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13735               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13736               FENcastlingRights[2] = whiteKingFile;
13737               break;
13738           case'Q':
13739               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13740               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13741               FENcastlingRights[2] = whiteKingFile;
13742               break;
13743           case'k':
13744               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13745               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13746               FENcastlingRights[5] = blackKingFile;
13747               break;
13748           case'q':
13749               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13750               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13751               FENcastlingRights[5] = blackKingFile;
13752           case '-':
13753               break;
13754           default: /* FRC castlings */
13755               if(c >= 'a') { /* black rights */
13756                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13757                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13758                   if(i == BOARD_RGHT) break;
13759                   FENcastlingRights[5] = i;
13760                   c -= AAA;
13761                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13762                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13763                   if(c > i)
13764                       FENcastlingRights[3] = c;
13765                   else
13766                       FENcastlingRights[4] = c;
13767               } else { /* white rights */
13768                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13769                     if(board[0][i] == WhiteKing) break;
13770                   if(i == BOARD_RGHT) break;
13771                   FENcastlingRights[2] = i;
13772                   c -= AAA - 'a' + 'A';
13773                   if(board[0][c] >= WhiteKing) break;
13774                   if(c > i)
13775                       FENcastlingRights[0] = c;
13776                   else
13777                       FENcastlingRights[1] = c;
13778               }
13779         }
13780       }
13781     if (appData.debugMode) {
13782         fprintf(debugFP, "FEN castling rights:");
13783         for(i=0; i<nrCastlingRights; i++)
13784         fprintf(debugFP, " %d", FENcastlingRights[i]);
13785         fprintf(debugFP, "\n");
13786     }
13787
13788       while(*p==' ') p++;
13789     }
13790
13791     /* read e.p. field in games that know e.p. capture */
13792     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13793        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13794       if(*p=='-') {
13795         p++; FENepStatus = EP_NONE;
13796       } else {
13797          char c = *p++ - AAA;
13798
13799          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13800          if(*p >= '0' && *p <='9') *p++;
13801          FENepStatus = c;
13802       }
13803     }
13804
13805
13806     if(sscanf(p, "%d", &i) == 1) {
13807         FENrulePlies = i; /* 50-move ply counter */
13808         /* (The move number is still ignored)    */
13809     }
13810
13811     return TRUE;
13812 }
13813       
13814 void
13815 EditPositionPasteFEN(char *fen)
13816 {
13817   if (fen != NULL) {
13818     Board initial_position;
13819
13820     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13821       DisplayError(_("Bad FEN position in clipboard"), 0);
13822       return ;
13823     } else {
13824       int savedBlackPlaysFirst = blackPlaysFirst;
13825       EditPositionEvent();
13826       blackPlaysFirst = savedBlackPlaysFirst;
13827       CopyBoard(boards[0], initial_position);
13828           /* [HGM] copy FEN attributes as well */
13829           {   int i;
13830               initialRulePlies = FENrulePlies;
13831               epStatus[0] = FENepStatus;
13832               for( i=0; i<nrCastlingRights; i++ )
13833                   castlingRights[0][i] = FENcastlingRights[i];
13834           }
13835       EditPositionDone();
13836       DisplayBothClocks();
13837       DrawPosition(FALSE, boards[currentMove]);
13838     }
13839   }
13840 }