94d0e81636bc74cf095f982544666f27693ae03b
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 #else /* not STDC_HEADERS */
81 # if HAVE_STRING_H
82 #  include <string.h>
83 # else /* not HAVE_STRING_H */
84 #  include <strings.h>
85 # endif /* not HAVE_STRING_H */
86 #endif /* not STDC_HEADERS */
87
88 #if HAVE_SYS_FCNTL_H
89 # include <sys/fcntl.h>
90 #else /* not HAVE_SYS_FCNTL_H */
91 # if HAVE_FCNTL_H
92 #  include <fcntl.h>
93 # endif /* HAVE_FCNTL_H */
94 #endif /* not HAVE_SYS_FCNTL_H */
95
96 #if TIME_WITH_SYS_TIME
97 # include <sys/time.h>
98 # include <time.h>
99 #else
100 # if HAVE_SYS_TIME_H
101 #  include <sys/time.h>
102 # else
103 #  include <time.h>
104 # endif
105 #endif
106
107 #if defined(_amigados) && !defined(__GNUC__)
108 struct timezone {
109     int tz_minuteswest;
110     int tz_dsttime;
111 };
112 extern int gettimeofday(struct timeval *, struct timezone *);
113 #endif
114
115 #if HAVE_UNISTD_H
116 # include <unistd.h>
117 #endif
118
119 #include "common.h"
120 #include "frontend.h"
121 #include "backend.h"
122 #include "parser.h"
123 #include "moves.h"
124 #if ZIPPY
125 # include "zippy.h"
126 #endif
127 #include "backendz.h"
128 #include "gettext.h" 
129  
130 #ifdef ENABLE_NLS 
131 # define _(s) gettext (s) 
132 # define N_(s) gettext_noop (s) 
133 #else 
134 # define _(s) (s) 
135 # define N_(s) s 
136 #endif 
137
138
139 /* A point in time */
140 typedef struct {
141     long sec;  /* Assuming this is >= 32 bits */
142     int ms;    /* Assuming this is >= 16 bits */
143 } TimeMark;
144
145 int establish P((void));
146 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
147                          char *buf, int count, int error));
148 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
149                       char *buf, int count, int error));
150 void SendToICS P((char *s));
151 void SendToICSDelayed P((char *s, long msdelay));
152 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
153                       int toX, int toY));
154 void HandleMachineMove P((char *message, ChessProgramState *cps));
155 int AutoPlayOneMove P((void));
156 int LoadGameOneMove P((ChessMove readAhead));
157 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
158 int LoadPositionFromFile P((char *filename, int n, char *title));
159 int SavePositionToFile P((char *filename));
160 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
161                   Board board, char *castle, char *ep));
162 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
163 void ShowMove P((int fromX, int fromY, int toX, int toY));
164 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
165                    /*char*/int promoChar));
166 void BackwardInner P((int target));
167 void ForwardInner P((int target));
168 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
169 void EditPositionDone P((void));
170 void PrintOpponents P((FILE *fp));
171 void PrintPosition P((FILE *fp, int move));
172 void StartChessProgram P((ChessProgramState *cps));
173 void SendToProgram P((char *message, ChessProgramState *cps));
174 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
175 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
176                            char *buf, int count, int error));
177 void SendTimeControl P((ChessProgramState *cps,
178                         int mps, long tc, int inc, int sd, int st));
179 char *TimeControlTagValue P((void));
180 void Attention P((ChessProgramState *cps));
181 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
182 void ResurrectChessProgram P((void));
183 void DisplayComment P((int moveNumber, char *text));
184 void DisplayMove P((int moveNumber));
185 void DisplayAnalysis P((void));
186
187 void ParseGameHistory P((char *game));
188 void ParseBoard12 P((char *string));
189 void StartClocks P((void));
190 void SwitchClocks P((void));
191 void StopClocks P((void));
192 void ResetClocks P((void));
193 char *PGNDate P((void));
194 void SetGameInfo P((void));
195 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
196 int RegisterMove P((void));
197 void MakeRegisteredMove P((void));
198 void TruncateGame P((void));
199 int looking_at P((char *, int *, char *));
200 void CopyPlayerNameIntoFileName P((char **, char *));
201 char *SavePart P((char *));
202 int SaveGameOldStyle P((FILE *));
203 int SaveGamePGN P((FILE *));
204 void GetTimeMark P((TimeMark *));
205 long SubtractTimeMarks P((TimeMark *, TimeMark *));
206 int CheckFlags P((void));
207 long NextTickLength P((long));
208 void CheckTimeControl P((void));
209 void show_bytes P((FILE *, char *, int));
210 int string_to_rating P((char *str));
211 void ParseFeatures P((char* args, ChessProgramState *cps));
212 void InitBackEnd3 P((void));
213 void FeatureDone P((ChessProgramState* cps, int val));
214 void InitChessProgram P((ChessProgramState *cps, int setup));
215 void OutputKibitz(int window, char *text);
216 int PerpetualChase(int first, int last);
217 int EngineOutputIsUp();
218 void InitDrawingSizes(int x, int y);
219
220 #ifdef WIN32
221        extern void ConsoleCreate();
222 #endif
223
224 ChessProgramState *WhitePlayer();
225 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
226 int VerifyDisplayMode P(());
227
228 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
229 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
230 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
231 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
232 extern char installDir[MSG_SIZ];
233
234 extern int tinyLayout, smallLayout;
235 ChessProgramStats programStats;
236 static int exiting = 0; /* [HGM] moved to top */
237 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
238 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
239 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
240 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
241 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
242 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
243 int opponentKibitzes;
244 int lastSavedGame; /* [HGM] save: ID of game */
245 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
246 extern int chatCount;
247 int chattingPartner;
248
249 /* States for ics_getting_history */
250 #define H_FALSE 0
251 #define H_REQUESTED 1
252 #define H_GOT_REQ_HEADER 2
253 #define H_GOT_UNREQ_HEADER 3
254 #define H_GETTING_MOVES 4
255 #define H_GOT_UNWANTED_HEADER 5
256
257 /* whosays values for GameEnds */
258 #define GE_ICS 0
259 #define GE_ENGINE 1
260 #define GE_PLAYER 2
261 #define GE_FILE 3
262 #define GE_XBOARD 4
263 #define GE_ENGINE1 5
264 #define GE_ENGINE2 6
265
266 /* Maximum number of games in a cmail message */
267 #define CMAIL_MAX_GAMES 20
268
269 /* Different types of move when calling RegisterMove */
270 #define CMAIL_MOVE   0
271 #define CMAIL_RESIGN 1
272 #define CMAIL_DRAW   2
273 #define CMAIL_ACCEPT 3
274
275 /* Different types of result to remember for each game */
276 #define CMAIL_NOT_RESULT 0
277 #define CMAIL_OLD_RESULT 1
278 #define CMAIL_NEW_RESULT 2
279
280 /* Telnet protocol constants */
281 #define TN_WILL 0373
282 #define TN_WONT 0374
283 #define TN_DO   0375
284 #define TN_DONT 0376
285 #define TN_IAC  0377
286 #define TN_ECHO 0001
287 #define TN_SGA  0003
288 #define TN_PORT 23
289
290 /* [AS] */
291 static char * safeStrCpy( char * dst, const char * src, size_t count )
292 {
293     assert( dst != NULL );
294     assert( src != NULL );
295     assert( count > 0 );
296
297     strncpy( dst, src, count );
298     dst[ count-1 ] = '\0';
299     return dst;
300 }
301
302 #if 0
303 //[HGM] for future use? Conditioned out for now to suppress warning.
304 static char * safeStrCat( char * dst, const char * src, size_t count )
305 {
306     size_t  dst_len;
307
308     assert( dst != NULL );
309     assert( src != NULL );
310     assert( count > 0 );
311
312     dst_len = strlen(dst);
313
314     assert( count > dst_len ); /* Buffer size must be greater than current length */
315
316     safeStrCpy( dst + dst_len, src, count - dst_len );
317
318     return dst;
319 }
320 #endif
321
322 /* Some compiler can't cast u64 to double
323  * This function do the job for us:
324
325  * We use the highest bit for cast, this only
326  * works if the highest bit is not
327  * in use (This should not happen)
328  *
329  * We used this for all compiler
330  */
331 double
332 u64ToDouble(u64 value)
333 {
334   double r;
335   u64 tmp = value & u64Const(0x7fffffffffffffff);
336   r = (double)(s64)tmp;
337   if (value & u64Const(0x8000000000000000))
338        r +=  9.2233720368547758080e18; /* 2^63 */
339  return r;
340 }
341
342 /* Fake up flags for now, as we aren't keeping track of castling
343    availability yet. [HGM] Change of logic: the flag now only
344    indicates the type of castlings allowed by the rule of the game.
345    The actual rights themselves are maintained in the array
346    castlingRights, as part of the game history, and are not probed
347    by this function.
348  */
349 int
350 PosFlags(index)
351 {
352   int flags = F_ALL_CASTLE_OK;
353   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
354   switch (gameInfo.variant) {
355   case VariantSuicide:
356     flags &= ~F_ALL_CASTLE_OK;
357   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
358     flags |= F_IGNORE_CHECK;
359   case VariantLosers:
360     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
361     break;
362   case VariantAtomic:
363     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
364     break;
365   case VariantKriegspiel:
366     flags |= F_KRIEGSPIEL_CAPTURE;
367     break;
368   case VariantCapaRandom: 
369   case VariantFischeRandom:
370     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
371   case VariantNoCastle:
372   case VariantShatranj:
373   case VariantCourier:
374     flags &= ~F_ALL_CASTLE_OK;
375     break;
376   default:
377     break;
378   }
379   return flags;
380 }
381
382 FILE *gameFileFP, *debugFP;
383
384 /* 
385     [AS] Note: sometimes, the sscanf() function is used to parse the input
386     into a fixed-size buffer. Because of this, we must be prepared to
387     receive strings as long as the size of the input buffer, which is currently
388     set to 4K for Windows and 8K for the rest.
389     So, we must either allocate sufficiently large buffers here, or
390     reduce the size of the input buffer in the input reading part.
391 */
392
393 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
394 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
395 char thinkOutput1[MSG_SIZ*10];
396
397 ChessProgramState first, second;
398
399 /* premove variables */
400 int premoveToX = 0;
401 int premoveToY = 0;
402 int premoveFromX = 0;
403 int premoveFromY = 0;
404 int premovePromoChar = 0;
405 int gotPremove = 0;
406 Boolean alarmSounded;
407 /* end premove variables */
408
409 char *ics_prefix = "$";
410 int ics_type = ICS_GENERIC;
411
412 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
413 int pauseExamForwardMostMove = 0;
414 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
415 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
416 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
417 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
418 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
419 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
420 int whiteFlag = FALSE, blackFlag = FALSE;
421 int userOfferedDraw = FALSE;
422 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
423 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
424 int cmailMoveType[CMAIL_MAX_GAMES];
425 long ics_clock_paused = 0;
426 ProcRef icsPR = NoProc, cmailPR = NoProc;
427 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
428 GameMode gameMode = BeginningOfGame;
429 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
430 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
431 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
432 int hiddenThinkOutputState = 0; /* [AS] */
433 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
434 int adjudicateLossPlies = 6;
435 char white_holding[64], black_holding[64];
436 TimeMark lastNodeCountTime;
437 long lastNodeCount=0;
438 int have_sent_ICS_logon = 0;
439 int movesPerSession;
440 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
441 long timeControl_2; /* [AS] Allow separate time controls */
442 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
443 long timeRemaining[2][MAX_MOVES];
444 int matchGame = 0;
445 TimeMark programStartTime;
446 char ics_handle[MSG_SIZ];
447 int have_set_title = 0;
448
449 /* animateTraining preserves the state of appData.animate
450  * when Training mode is activated. This allows the
451  * response to be animated when appData.animate == TRUE and
452  * appData.animateDragging == TRUE.
453  */
454 Boolean animateTraining;
455
456 GameInfo gameInfo;
457
458 AppData appData;
459
460 Board boards[MAX_MOVES];
461 /* [HGM] Following 7 needed for accurate legality tests: */
462 signed char  epStatus[MAX_MOVES];
463 signed char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
464 signed char  castlingRank[BOARD_SIZE]; // and corresponding ranks
465 signed char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
466 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
467 int   initialRulePlies, FENrulePlies;
468 char  FENepStatus;
469 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
470 int loadFlag = 0; 
471 int shuffleOpenings;
472
473 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
474     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
475         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
476     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
477         BlackKing, BlackBishop, BlackKnight, BlackRook }
478 };
479
480 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
481     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
482         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
483     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
484         BlackKing, BlackKing, BlackKnight, BlackRook }
485 };
486
487 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
488     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
489         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
490     { BlackRook, BlackMan, BlackBishop, BlackQueen,
491         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
492 };
493
494 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
495     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
496         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
497     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
498         BlackKing, BlackBishop, BlackKnight, BlackRook }
499 };
500
501 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
502     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
503         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
504     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
505         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
506 };
507
508
509 #if (BOARD_SIZE>=10)
510 ChessSquare ShogiArray[2][BOARD_SIZE] = {
511     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
512         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
513     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
514         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
515 };
516
517 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
518     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
519         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
520     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
521         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
522 };
523
524 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
525     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
526         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
527     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
528         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
529 };
530
531 ChessSquare GreatArray[2][BOARD_SIZE] = {
532     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
533         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
534     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
535         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
536 };
537
538 ChessSquare JanusArray[2][BOARD_SIZE] = {
539     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
540         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
541     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
542         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
543 };
544
545 #ifdef GOTHIC
546 ChessSquare GothicArray[2][BOARD_SIZE] = {
547     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
548         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
549     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
550         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
551 };
552 #else // !GOTHIC
553 #define GothicArray CapablancaArray
554 #endif // !GOTHIC
555
556 #ifdef FALCON
557 ChessSquare FalconArray[2][BOARD_SIZE] = {
558     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
559         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
560     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
561         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
562 };
563 #else // !FALCON
564 #define FalconArray CapablancaArray
565 #endif // !FALCON
566
567 #else // !(BOARD_SIZE>=10)
568 #define XiangqiPosition FIDEArray
569 #define CapablancaArray FIDEArray
570 #define GothicArray FIDEArray
571 #define GreatArray FIDEArray
572 #endif // !(BOARD_SIZE>=10)
573
574 #if (BOARD_SIZE>=12)
575 ChessSquare CourierArray[2][BOARD_SIZE] = {
576     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
577         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
578     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
579         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
580 };
581 #else // !(BOARD_SIZE>=12)
582 #define CourierArray CapablancaArray
583 #endif // !(BOARD_SIZE>=12)
584
585
586 Board initialPosition;
587
588
589 /* Convert str to a rating. Checks for special cases of "----",
590
591    "++++", etc. Also strips ()'s */
592 int
593 string_to_rating(str)
594   char *str;
595 {
596   while(*str && !isdigit(*str)) ++str;
597   if (!*str)
598     return 0;   /* One of the special "no rating" cases */
599   else
600     return atoi(str);
601 }
602
603 void
604 ClearProgramStats()
605 {
606     /* Init programStats */
607     programStats.movelist[0] = 0;
608     programStats.depth = 0;
609     programStats.nr_moves = 0;
610     programStats.moves_left = 0;
611     programStats.nodes = 0;
612     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
613     programStats.score = 0;
614     programStats.got_only_move = 0;
615     programStats.got_fail = 0;
616     programStats.line_is_book = 0;
617 }
618
619 void
620 InitBackEnd1()
621 {
622     int matched, min, sec;
623
624     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
625
626     GetTimeMark(&programStartTime);
627     srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
628
629     ClearProgramStats();
630     programStats.ok_to_send = 1;
631     programStats.seen_stat = 0;
632
633     /*
634      * Initialize game list
635      */
636     ListNew(&gameList);
637
638
639     /*
640      * Internet chess server status
641      */
642     if (appData.icsActive) {
643         appData.matchMode = FALSE;
644         appData.matchGames = 0;
645 #if ZIPPY       
646         appData.noChessProgram = !appData.zippyPlay;
647 #else
648         appData.zippyPlay = FALSE;
649         appData.zippyTalk = FALSE;
650         appData.noChessProgram = TRUE;
651 #endif
652         if (*appData.icsHelper != NULLCHAR) {
653             appData.useTelnet = TRUE;
654             appData.telnetProgram = appData.icsHelper;
655         }
656     } else {
657         appData.zippyTalk = appData.zippyPlay = FALSE;
658     }
659
660     /* [AS] Initialize pv info list [HGM] and game state */
661     {
662         int i, j;
663
664         for( i=0; i<MAX_MOVES; i++ ) {
665             pvInfoList[i].depth = -1;
666             epStatus[i]=EP_NONE;
667             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
668         }
669     }
670
671     /*
672      * Parse timeControl resource
673      */
674     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
675                           appData.movesPerSession)) {
676         char buf[MSG_SIZ];
677         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
678         DisplayFatalError(buf, 0, 2);
679     }
680
681     /*
682      * Parse searchTime resource
683      */
684     if (*appData.searchTime != NULLCHAR) {
685         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
686         if (matched == 1) {
687             searchTime = min * 60;
688         } else if (matched == 2) {
689             searchTime = min * 60 + sec;
690         } else {
691             char buf[MSG_SIZ];
692             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
693             DisplayFatalError(buf, 0, 2);
694         }
695     }
696
697     /* [AS] Adjudication threshold */
698     adjudicateLossThreshold = appData.adjudicateLossThreshold;
699     
700     first.which = "first";
701     second.which = "second";
702     first.maybeThinking = second.maybeThinking = FALSE;
703     first.pr = second.pr = NoProc;
704     first.isr = second.isr = NULL;
705     first.sendTime = second.sendTime = 2;
706     first.sendDrawOffers = 1;
707     if (appData.firstPlaysBlack) {
708         first.twoMachinesColor = "black\n";
709         second.twoMachinesColor = "white\n";
710     } else {
711         first.twoMachinesColor = "white\n";
712         second.twoMachinesColor = "black\n";
713     }
714     first.program = appData.firstChessProgram;
715     second.program = appData.secondChessProgram;
716     first.host = appData.firstHost;
717     second.host = appData.secondHost;
718     first.dir = appData.firstDirectory;
719     second.dir = appData.secondDirectory;
720     first.other = &second;
721     second.other = &first;
722     first.initString = appData.initString;
723     second.initString = appData.secondInitString;
724     first.computerString = appData.firstComputerString;
725     second.computerString = appData.secondComputerString;
726     first.useSigint = second.useSigint = TRUE;
727     first.useSigterm = second.useSigterm = TRUE;
728     first.reuse = appData.reuseFirst;
729     second.reuse = appData.reuseSecond;
730     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
731     second.nps = appData.secondNPS;
732     first.useSetboard = second.useSetboard = FALSE;
733     first.useSAN = second.useSAN = FALSE;
734     first.usePing = second.usePing = FALSE;
735     first.lastPing = second.lastPing = 0;
736     first.lastPong = second.lastPong = 0;
737     first.usePlayother = second.usePlayother = FALSE;
738     first.useColors = second.useColors = TRUE;
739     first.useUsermove = second.useUsermove = FALSE;
740     first.sendICS = second.sendICS = FALSE;
741     first.sendName = second.sendName = appData.icsActive;
742     first.sdKludge = second.sdKludge = FALSE;
743     first.stKludge = second.stKludge = FALSE;
744     TidyProgramName(first.program, first.host, first.tidy);
745     TidyProgramName(second.program, second.host, second.tidy);
746     first.matchWins = second.matchWins = 0;
747     strcpy(first.variants, appData.variant);
748     strcpy(second.variants, appData.variant);
749     first.analysisSupport = second.analysisSupport = 2; /* detect */
750     first.analyzing = second.analyzing = FALSE;
751     first.initDone = second.initDone = FALSE;
752
753     /* New features added by Tord: */
754     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
755     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
756     /* End of new features added by Tord. */
757     first.fenOverride  = appData.fenOverride1;
758     second.fenOverride = appData.fenOverride2;
759
760     /* [HGM] time odds: set factor for each machine */
761     first.timeOdds  = appData.firstTimeOdds;
762     second.timeOdds = appData.secondTimeOdds;
763     { int norm = 1;
764         if(appData.timeOddsMode) {
765             norm = first.timeOdds;
766             if(norm > second.timeOdds) norm = second.timeOdds;
767         }
768         first.timeOdds /= norm;
769         second.timeOdds /= norm;
770     }
771
772     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
773     first.accumulateTC = appData.firstAccumulateTC;
774     second.accumulateTC = appData.secondAccumulateTC;
775     first.maxNrOfSessions = second.maxNrOfSessions = 1;
776
777     /* [HGM] debug */
778     first.debug = second.debug = FALSE;
779     first.supportsNPS = second.supportsNPS = UNKNOWN;
780
781     /* [HGM] options */
782     first.optionSettings  = appData.firstOptions;
783     second.optionSettings = appData.secondOptions;
784
785     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
786     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
787     first.isUCI = appData.firstIsUCI; /* [AS] */
788     second.isUCI = appData.secondIsUCI; /* [AS] */
789     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
790     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
791
792     if (appData.firstProtocolVersion > PROTOVER ||
793         appData.firstProtocolVersion < 1) {
794       char buf[MSG_SIZ];
795       sprintf(buf, _("protocol version %d not supported"),
796               appData.firstProtocolVersion);
797       DisplayFatalError(buf, 0, 2);
798     } else {
799       first.protocolVersion = appData.firstProtocolVersion;
800     }
801
802     if (appData.secondProtocolVersion > PROTOVER ||
803         appData.secondProtocolVersion < 1) {
804       char buf[MSG_SIZ];
805       sprintf(buf, _("protocol version %d not supported"),
806               appData.secondProtocolVersion);
807       DisplayFatalError(buf, 0, 2);
808     } else {
809       second.protocolVersion = appData.secondProtocolVersion;
810     }
811
812     if (appData.icsActive) {
813         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
814     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
815         appData.clockMode = FALSE;
816         first.sendTime = second.sendTime = 0;
817     }
818     
819 #if ZIPPY
820     /* Override some settings from environment variables, for backward
821        compatibility.  Unfortunately it's not feasible to have the env
822        vars just set defaults, at least in xboard.  Ugh.
823     */
824     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
825       ZippyInit();
826     }
827 #endif
828     
829     if (appData.noChessProgram) {
830         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
831         sprintf(programVersion, "%s", PACKAGE_STRING);
832     } else {
833 #if 0
834         char *p, *q;
835         q = first.program;
836         while (*q != ' ' && *q != NULLCHAR) q++;
837         p = q;
838         while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] backslash added */
839         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING + (q - p));
840         sprintf(programVersion, "%s + ", PACKAGE_STRING);
841         strncat(programVersion, p, q - p);
842 #else
843         /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
844         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
845         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
846 #endif
847     }
848
849     if (!appData.icsActive) {
850       char buf[MSG_SIZ];
851       /* Check for variants that are supported only in ICS mode,
852          or not at all.  Some that are accepted here nevertheless
853          have bugs; see comments below.
854       */
855       VariantClass variant = StringToVariant(appData.variant);
856       switch (variant) {
857       case VariantBughouse:     /* need four players and two boards */
858       case VariantKriegspiel:   /* need to hide pieces and move details */
859       /* case VariantFischeRandom: (Fabien: moved below) */
860         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
861         DisplayFatalError(buf, 0, 2);
862         return;
863
864       case VariantUnknown:
865       case VariantLoadable:
866       case Variant29:
867       case Variant30:
868       case Variant31:
869       case Variant32:
870       case Variant33:
871       case Variant34:
872       case Variant35:
873       case Variant36:
874       default:
875         sprintf(buf, _("Unknown variant name %s"), appData.variant);
876         DisplayFatalError(buf, 0, 2);
877         return;
878
879       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
880       case VariantFairy:      /* [HGM] TestLegality definitely off! */
881       case VariantGothic:     /* [HGM] should work */
882       case VariantCapablanca: /* [HGM] should work */
883       case VariantCourier:    /* [HGM] initial forced moves not implemented */
884       case VariantShogi:      /* [HGM] drops not tested for legality */
885       case VariantKnightmate: /* [HGM] should work */
886       case VariantCylinder:   /* [HGM] untested */
887       case VariantFalcon:     /* [HGM] untested */
888       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
889                                  offboard interposition not understood */
890       case VariantNormal:     /* definitely works! */
891       case VariantWildCastle: /* pieces not automatically shuffled */
892       case VariantNoCastle:   /* pieces not automatically shuffled */
893       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
894       case VariantLosers:     /* should work except for win condition,
895                                  and doesn't know captures are mandatory */
896       case VariantSuicide:    /* should work except for win condition,
897                                  and doesn't know captures are mandatory */
898       case VariantGiveaway:   /* should work except for win condition,
899                                  and doesn't know captures are mandatory */
900       case VariantTwoKings:   /* should work */
901       case VariantAtomic:     /* should work except for win condition */
902       case Variant3Check:     /* should work except for win condition */
903       case VariantShatranj:   /* should work except for all win conditions */
904       case VariantBerolina:   /* might work if TestLegality is off */
905       case VariantCapaRandom: /* should work */
906       case VariantJanus:      /* should work */
907       case VariantSuper:      /* experimental */
908       case VariantGreat:      /* experimental, requires legality testing to be off */
909         break;
910       }
911     }
912
913     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
914     InitEngineUCI( installDir, &second );
915 }
916
917 int NextIntegerFromString( char ** str, long * value )
918 {
919     int result = -1;
920     char * s = *str;
921
922     while( *s == ' ' || *s == '\t' ) {
923         s++;
924     }
925
926     *value = 0;
927
928     if( *s >= '0' && *s <= '9' ) {
929         while( *s >= '0' && *s <= '9' ) {
930             *value = *value * 10 + (*s - '0');
931             s++;
932         }
933
934         result = 0;
935     }
936
937     *str = s;
938
939     return result;
940 }
941
942 int NextTimeControlFromString( char ** str, long * value )
943 {
944     long temp;
945     int result = NextIntegerFromString( str, &temp );
946
947     if( result == 0 ) {
948         *value = temp * 60; /* Minutes */
949         if( **str == ':' ) {
950             (*str)++;
951             result = NextIntegerFromString( str, &temp );
952             *value += temp; /* Seconds */
953         }
954     }
955
956     return result;
957 }
958
959 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
960 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
961     int result = -1; long temp, temp2;
962
963     if(**str != '+') return -1; // old params remain in force!
964     (*str)++;
965     if( NextTimeControlFromString( str, &temp ) ) return -1;
966
967     if(**str != '/') {
968         /* time only: incremental or sudden-death time control */
969         if(**str == '+') { /* increment follows; read it */
970             (*str)++;
971             if(result = NextIntegerFromString( str, &temp2)) return -1;
972             *inc = temp2 * 1000;
973         } else *inc = 0;
974         *moves = 0; *tc = temp * 1000; 
975         return 0;
976     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
977
978     (*str)++; /* classical time control */
979     result = NextTimeControlFromString( str, &temp2);
980     if(result == 0) {
981         *moves = temp/60;
982         *tc    = temp2 * 1000;
983         *inc   = 0;
984     }
985     return result;
986 }
987
988 int GetTimeQuota(int movenr)
989 {   /* [HGM] get time to add from the multi-session time-control string */
990     int moves=1; /* kludge to force reading of first session */
991     long time, increment;
992     char *s = fullTimeControlString;
993
994     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
995     do {
996         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
997         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
998         if(movenr == -1) return time;    /* last move before new session     */
999         if(!moves) return increment;     /* current session is incremental   */
1000         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1001     } while(movenr >= -1);               /* try again for next session       */
1002
1003     return 0; // no new time quota on this move
1004 }
1005
1006 int
1007 ParseTimeControl(tc, ti, mps)
1008      char *tc;
1009      int ti;
1010      int mps;
1011 {
1012 #if 0
1013     int matched, min, sec;
1014
1015     matched = sscanf(tc, "%d:%d", &min, &sec);
1016     if (matched == 1) {
1017         timeControl = min * 60 * 1000;
1018     } else if (matched == 2) {
1019         timeControl = (min * 60 + sec) * 1000;
1020     } else {
1021         return FALSE;
1022     }
1023 #else
1024     long tc1;
1025     long tc2;
1026     char buf[MSG_SIZ];
1027
1028     if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1029     if(ti > 0) {
1030         if(mps)
1031              sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1032         else sprintf(buf, "+%s+%d", tc, ti);
1033     } else {
1034         if(mps)
1035              sprintf(buf, "+%d/%s", mps, tc);
1036         else sprintf(buf, "+%s", tc);
1037     }
1038     fullTimeControlString = StrSave(buf);
1039
1040     if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1041         return FALSE;
1042     }
1043
1044     if( *tc == '/' ) {
1045         /* Parse second time control */
1046         tc++;
1047
1048         if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1049             return FALSE;
1050         }
1051
1052         if( tc2 == 0 ) {
1053             return FALSE;
1054         }
1055
1056         timeControl_2 = tc2 * 1000;
1057     }
1058     else {
1059         timeControl_2 = 0;
1060     }
1061
1062     if( tc1 == 0 ) {
1063         return FALSE;
1064     }
1065
1066     timeControl = tc1 * 1000;
1067 #endif
1068
1069     if (ti >= 0) {
1070         timeIncrement = ti * 1000;  /* convert to ms */
1071         movesPerSession = 0;
1072     } else {
1073         timeIncrement = 0;
1074         movesPerSession = mps;
1075     }
1076     return TRUE;
1077 }
1078
1079 void
1080 InitBackEnd2()
1081 {
1082     if (appData.debugMode) {
1083         fprintf(debugFP, "%s\n", programVersion);
1084     }
1085
1086     if (appData.matchGames > 0) {
1087         appData.matchMode = TRUE;
1088     } else if (appData.matchMode) {
1089         appData.matchGames = 1;
1090     }
1091     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1092         appData.matchGames = appData.sameColorGames;
1093     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1094         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1095         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1096     }
1097     Reset(TRUE, FALSE);
1098     if (appData.noChessProgram || first.protocolVersion == 1) {
1099       InitBackEnd3();
1100     } else {
1101       /* kludge: allow timeout for initial "feature" commands */
1102       FreezeUI();
1103       DisplayMessage("", _("Starting chess program"));
1104       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1105     }
1106 }
1107
1108 void
1109 InitBackEnd3 P((void))
1110 {
1111     GameMode initialMode;
1112     char buf[MSG_SIZ];
1113     int err;
1114
1115     InitChessProgram(&first, startedFromSetupPosition);
1116
1117
1118     if (appData.icsActive) {
1119 #ifdef WIN32
1120         /* [DM] Make a console window if needed [HGM] merged ifs */
1121         ConsoleCreate(); 
1122 #endif
1123         err = establish();
1124         if (err != 0) {
1125             if (*appData.icsCommPort != NULLCHAR) {
1126                 sprintf(buf, _("Could not open comm port %s"),  
1127                         appData.icsCommPort);
1128             } else {
1129                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1130                         appData.icsHost, appData.icsPort);
1131             }
1132             DisplayFatalError(buf, err, 1);
1133             return;
1134         }
1135         SetICSMode();
1136         telnetISR =
1137           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1138         fromUserISR =
1139           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1140     } else if (appData.noChessProgram) {
1141         SetNCPMode();
1142     } else {
1143         SetGNUMode();
1144     }
1145
1146     if (*appData.cmailGameName != NULLCHAR) {
1147         SetCmailMode();
1148         OpenLoopback(&cmailPR);
1149         cmailISR =
1150           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1151     }
1152     
1153     ThawUI();
1154     DisplayMessage("", "");
1155     if (StrCaseCmp(appData.initialMode, "") == 0) {
1156       initialMode = BeginningOfGame;
1157     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1158       initialMode = TwoMachinesPlay;
1159     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1160       initialMode = AnalyzeFile; 
1161     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1162       initialMode = AnalyzeMode;
1163     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1164       initialMode = MachinePlaysWhite;
1165     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1166       initialMode = MachinePlaysBlack;
1167     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1168       initialMode = EditGame;
1169     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1170       initialMode = EditPosition;
1171     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1172       initialMode = Training;
1173     } else {
1174       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1175       DisplayFatalError(buf, 0, 2);
1176       return;
1177     }
1178
1179     if (appData.matchMode) {
1180         /* Set up machine vs. machine match */
1181         if (appData.noChessProgram) {
1182             DisplayFatalError(_("Can't have a match with no chess programs"),
1183                               0, 2);
1184             return;
1185         }
1186         matchMode = TRUE;
1187         matchGame = 1;
1188         if (*appData.loadGameFile != NULLCHAR) {
1189             int index = appData.loadGameIndex; // [HGM] autoinc
1190             if(index<0) lastIndex = index = 1;
1191             if (!LoadGameFromFile(appData.loadGameFile,
1192                                   index,
1193                                   appData.loadGameFile, FALSE)) {
1194                 DisplayFatalError(_("Bad game file"), 0, 1);
1195                 return;
1196             }
1197         } else if (*appData.loadPositionFile != NULLCHAR) {
1198             int index = appData.loadPositionIndex; // [HGM] autoinc
1199             if(index<0) lastIndex = index = 1;
1200             if (!LoadPositionFromFile(appData.loadPositionFile,
1201                                       index,
1202                                       appData.loadPositionFile)) {
1203                 DisplayFatalError(_("Bad position file"), 0, 1);
1204                 return;
1205             }
1206         }
1207         TwoMachinesEvent();
1208     } else if (*appData.cmailGameName != NULLCHAR) {
1209         /* Set up cmail mode */
1210         ReloadCmailMsgEvent(TRUE);
1211     } else {
1212         /* Set up other modes */
1213         if (initialMode == AnalyzeFile) {
1214           if (*appData.loadGameFile == NULLCHAR) {
1215             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1216             return;
1217           }
1218         }
1219         if (*appData.loadGameFile != NULLCHAR) {
1220             (void) LoadGameFromFile(appData.loadGameFile,
1221                                     appData.loadGameIndex,
1222                                     appData.loadGameFile, TRUE);
1223         } else if (*appData.loadPositionFile != NULLCHAR) {
1224             (void) LoadPositionFromFile(appData.loadPositionFile,
1225                                         appData.loadPositionIndex,
1226                                         appData.loadPositionFile);
1227             /* [HGM] try to make self-starting even after FEN load */
1228             /* to allow automatic setup of fairy variants with wtm */
1229             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1230                 gameMode = BeginningOfGame;
1231                 setboardSpoiledMachineBlack = 1;
1232             }
1233             /* [HGM] loadPos: make that every new game uses the setup */
1234             /* from file as long as we do not switch variant          */
1235             if(!blackPlaysFirst) { int i;
1236                 startedFromPositionFile = TRUE;
1237                 CopyBoard(filePosition, boards[0]);
1238                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1239             }
1240         }
1241         if (initialMode == AnalyzeMode) {
1242           if (appData.noChessProgram) {
1243             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1244             return;
1245           }
1246           if (appData.icsActive) {
1247             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1248             return;
1249           }
1250           AnalyzeModeEvent();
1251         } else if (initialMode == AnalyzeFile) {
1252           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1253           ShowThinkingEvent();
1254           AnalyzeFileEvent();
1255           AnalysisPeriodicEvent(1);
1256         } else if (initialMode == MachinePlaysWhite) {
1257           if (appData.noChessProgram) {
1258             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1259                               0, 2);
1260             return;
1261           }
1262           if (appData.icsActive) {
1263             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1264                               0, 2);
1265             return;
1266           }
1267           MachineWhiteEvent();
1268         } else if (initialMode == MachinePlaysBlack) {
1269           if (appData.noChessProgram) {
1270             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1271                               0, 2);
1272             return;
1273           }
1274           if (appData.icsActive) {
1275             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1276                               0, 2);
1277             return;
1278           }
1279           MachineBlackEvent();
1280         } else if (initialMode == TwoMachinesPlay) {
1281           if (appData.noChessProgram) {
1282             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1283                               0, 2);
1284             return;
1285           }
1286           if (appData.icsActive) {
1287             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1288                               0, 2);
1289             return;
1290           }
1291           TwoMachinesEvent();
1292         } else if (initialMode == EditGame) {
1293           EditGameEvent();
1294         } else if (initialMode == EditPosition) {
1295           EditPositionEvent();
1296         } else if (initialMode == Training) {
1297           if (*appData.loadGameFile == NULLCHAR) {
1298             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1299             return;
1300           }
1301           TrainingEvent();
1302         }
1303     }
1304 }
1305
1306 /*
1307  * Establish will establish a contact to a remote host.port.
1308  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1309  *  used to talk to the host.
1310  * Returns 0 if okay, error code if not.
1311  */
1312 int
1313 establish()
1314 {
1315     char buf[MSG_SIZ];
1316
1317     if (*appData.icsCommPort != NULLCHAR) {
1318         /* Talk to the host through a serial comm port */
1319         return OpenCommPort(appData.icsCommPort, &icsPR);
1320
1321     } else if (*appData.gateway != NULLCHAR) {
1322         if (*appData.remoteShell == NULLCHAR) {
1323             /* Use the rcmd protocol to run telnet program on a gateway host */
1324             snprintf(buf, sizeof(buf), "%s %s %s",
1325                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1326             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1327
1328         } else {
1329             /* Use the rsh program to run telnet program on a gateway host */
1330             if (*appData.remoteUser == NULLCHAR) {
1331                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1332                         appData.gateway, appData.telnetProgram,
1333                         appData.icsHost, appData.icsPort);
1334             } else {
1335                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1336                         appData.remoteShell, appData.gateway, 
1337                         appData.remoteUser, appData.telnetProgram,
1338                         appData.icsHost, appData.icsPort);
1339             }
1340             return StartChildProcess(buf, "", &icsPR);
1341
1342         }
1343     } else if (appData.useTelnet) {
1344         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1345
1346     } else {
1347         /* TCP socket interface differs somewhat between
1348            Unix and NT; handle details in the front end.
1349            */
1350         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1351     }
1352 }
1353
1354 void
1355 show_bytes(fp, buf, count)
1356      FILE *fp;
1357      char *buf;
1358      int count;
1359 {
1360     while (count--) {
1361         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1362             fprintf(fp, "\\%03o", *buf & 0xff);
1363         } else {
1364             putc(*buf, fp);
1365         }
1366         buf++;
1367     }
1368     fflush(fp);
1369 }
1370
1371 /* Returns an errno value */
1372 int
1373 OutputMaybeTelnet(pr, message, count, outError)
1374      ProcRef pr;
1375      char *message;
1376      int count;
1377      int *outError;
1378 {
1379     char buf[8192], *p, *q, *buflim;
1380     int left, newcount, outcount;
1381
1382     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1383         *appData.gateway != NULLCHAR) {
1384         if (appData.debugMode) {
1385             fprintf(debugFP, ">ICS: ");
1386             show_bytes(debugFP, message, count);
1387             fprintf(debugFP, "\n");
1388         }
1389         return OutputToProcess(pr, message, count, outError);
1390     }
1391
1392     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1393     p = message;
1394     q = buf;
1395     left = count;
1396     newcount = 0;
1397     while (left) {
1398         if (q >= buflim) {
1399             if (appData.debugMode) {
1400                 fprintf(debugFP, ">ICS: ");
1401                 show_bytes(debugFP, buf, newcount);
1402                 fprintf(debugFP, "\n");
1403             }
1404             outcount = OutputToProcess(pr, buf, newcount, outError);
1405             if (outcount < newcount) return -1; /* to be sure */
1406             q = buf;
1407             newcount = 0;
1408         }
1409         if (*p == '\n') {
1410             *q++ = '\r';
1411             newcount++;
1412         } else if (((unsigned char) *p) == TN_IAC) {
1413             *q++ = (char) TN_IAC;
1414             newcount ++;
1415         }
1416         *q++ = *p++;
1417         newcount++;
1418         left--;
1419     }
1420     if (appData.debugMode) {
1421         fprintf(debugFP, ">ICS: ");
1422         show_bytes(debugFP, buf, newcount);
1423         fprintf(debugFP, "\n");
1424     }
1425     outcount = OutputToProcess(pr, buf, newcount, outError);
1426     if (outcount < newcount) return -1; /* to be sure */
1427     return count;
1428 }
1429
1430 void
1431 read_from_player(isr, closure, message, count, error)
1432      InputSourceRef isr;
1433      VOIDSTAR closure;
1434      char *message;
1435      int count;
1436      int error;
1437 {
1438     int outError, outCount;
1439     static int gotEof = 0;
1440
1441     /* Pass data read from player on to ICS */
1442     if (count > 0) {
1443         gotEof = 0;
1444         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1445         if (outCount < count) {
1446             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1447         }
1448     } else if (count < 0) {
1449         RemoveInputSource(isr);
1450         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1451     } else if (gotEof++ > 0) {
1452         RemoveInputSource(isr);
1453         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1454     }
1455 }
1456
1457 void
1458 KeepAlive()
1459 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1460     SendToICS("date\n");
1461     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1462 }
1463
1464 void
1465 SendToICS(s)
1466      char *s;
1467 {
1468     int count, outCount, outError;
1469
1470     if (icsPR == NULL) return;
1471
1472     count = strlen(s);
1473     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1474     if (outCount < count) {
1475         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1476     }
1477 }
1478
1479 /* This is used for sending logon scripts to the ICS. Sending
1480    without a delay causes problems when using timestamp on ICC
1481    (at least on my machine). */
1482 void
1483 SendToICSDelayed(s,msdelay)
1484      char *s;
1485      long msdelay;
1486 {
1487     int count, outCount, outError;
1488
1489     if (icsPR == NULL) return;
1490
1491     count = strlen(s);
1492     if (appData.debugMode) {
1493         fprintf(debugFP, ">ICS: ");
1494         show_bytes(debugFP, s, count);
1495         fprintf(debugFP, "\n");
1496     }
1497     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1498                                       msdelay);
1499     if (outCount < count) {
1500         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1501     }
1502 }
1503
1504
1505 /* Remove all highlighting escape sequences in s
1506    Also deletes any suffix starting with '(' 
1507    */
1508 char *
1509 StripHighlightAndTitle(s)
1510      char *s;
1511 {
1512     static char retbuf[MSG_SIZ];
1513     char *p = retbuf;
1514
1515     while (*s != NULLCHAR) {
1516         while (*s == '\033') {
1517             while (*s != NULLCHAR && !isalpha(*s)) s++;
1518             if (*s != NULLCHAR) s++;
1519         }
1520         while (*s != NULLCHAR && *s != '\033') {
1521             if (*s == '(' || *s == '[') {
1522                 *p = NULLCHAR;
1523                 return retbuf;
1524             }
1525             *p++ = *s++;
1526         }
1527     }
1528     *p = NULLCHAR;
1529     return retbuf;
1530 }
1531
1532 /* Remove all highlighting escape sequences in s */
1533 char *
1534 StripHighlight(s)
1535      char *s;
1536 {
1537     static char retbuf[MSG_SIZ];
1538     char *p = retbuf;
1539
1540     while (*s != NULLCHAR) {
1541         while (*s == '\033') {
1542             while (*s != NULLCHAR && !isalpha(*s)) s++;
1543             if (*s != NULLCHAR) s++;
1544         }
1545         while (*s != NULLCHAR && *s != '\033') {
1546             *p++ = *s++;
1547         }
1548     }
1549     *p = NULLCHAR;
1550     return retbuf;
1551 }
1552
1553 char *variantNames[] = VARIANT_NAMES;
1554 char *
1555 VariantName(v)
1556      VariantClass v;
1557 {
1558     return variantNames[v];
1559 }
1560
1561
1562 /* Identify a variant from the strings the chess servers use or the
1563    PGN Variant tag names we use. */
1564 VariantClass
1565 StringToVariant(e)
1566      char *e;
1567 {
1568     char *p;
1569     int wnum = -1;
1570     VariantClass v = VariantNormal;
1571     int i, found = FALSE;
1572     char buf[MSG_SIZ];
1573
1574     if (!e) return v;
1575
1576     /* [HGM] skip over optional board-size prefixes */
1577     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1578         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1579         while( *e++ != '_');
1580     }
1581
1582     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1583       if (StrCaseStr(e, variantNames[i])) {
1584         v = (VariantClass) i;
1585         found = TRUE;
1586         break;
1587       }
1588     }
1589
1590     if (!found) {
1591       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1592           || StrCaseStr(e, "wild/fr") 
1593           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1594         v = VariantFischeRandom;
1595       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1596                  (i = 1, p = StrCaseStr(e, "w"))) {
1597         p += i;
1598         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1599         if (isdigit(*p)) {
1600           wnum = atoi(p);
1601         } else {
1602           wnum = -1;
1603         }
1604         switch (wnum) {
1605         case 0: /* FICS only, actually */
1606         case 1:
1607           /* Castling legal even if K starts on d-file */
1608           v = VariantWildCastle;
1609           break;
1610         case 2:
1611         case 3:
1612         case 4:
1613           /* Castling illegal even if K & R happen to start in
1614              normal positions. */
1615           v = VariantNoCastle;
1616           break;
1617         case 5:
1618         case 7:
1619         case 8:
1620         case 10:
1621         case 11:
1622         case 12:
1623         case 13:
1624         case 14:
1625         case 15:
1626         case 18:
1627         case 19:
1628           /* Castling legal iff K & R start in normal positions */
1629           v = VariantNormal;
1630           break;
1631         case 6:
1632         case 20:
1633         case 21:
1634           /* Special wilds for position setup; unclear what to do here */
1635           v = VariantLoadable;
1636           break;
1637         case 9:
1638           /* Bizarre ICC game */
1639           v = VariantTwoKings;
1640           break;
1641         case 16:
1642           v = VariantKriegspiel;
1643           break;
1644         case 17:
1645           v = VariantLosers;
1646           break;
1647         case 22:
1648           v = VariantFischeRandom;
1649           break;
1650         case 23:
1651           v = VariantCrazyhouse;
1652           break;
1653         case 24:
1654           v = VariantBughouse;
1655           break;
1656         case 25:
1657           v = Variant3Check;
1658           break;
1659         case 26:
1660           /* Not quite the same as FICS suicide! */
1661           v = VariantGiveaway;
1662           break;
1663         case 27:
1664           v = VariantAtomic;
1665           break;
1666         case 28:
1667           v = VariantShatranj;
1668           break;
1669
1670         /* Temporary names for future ICC types.  The name *will* change in 
1671            the next xboard/WinBoard release after ICC defines it. */
1672         case 29:
1673           v = Variant29;
1674           break;
1675         case 30:
1676           v = Variant30;
1677           break;
1678         case 31:
1679           v = Variant31;
1680           break;
1681         case 32:
1682           v = Variant32;
1683           break;
1684         case 33:
1685           v = Variant33;
1686           break;
1687         case 34:
1688           v = Variant34;
1689           break;
1690         case 35:
1691           v = Variant35;
1692           break;
1693         case 36:
1694           v = Variant36;
1695           break;
1696         case 37:
1697           v = VariantShogi;
1698           break;
1699         case 38:
1700           v = VariantXiangqi;
1701           break;
1702         case 39:
1703           v = VariantCourier;
1704           break;
1705         case 40:
1706           v = VariantGothic;
1707           break;
1708         case 41:
1709           v = VariantCapablanca;
1710           break;
1711         case 42:
1712           v = VariantKnightmate;
1713           break;
1714         case 43:
1715           v = VariantFairy;
1716           break;
1717         case 44:
1718           v = VariantCylinder;
1719           break;
1720         case 45:
1721           v = VariantFalcon;
1722           break;
1723         case 46:
1724           v = VariantCapaRandom;
1725           break;
1726         case 47:
1727           v = VariantBerolina;
1728           break;
1729         case 48:
1730           v = VariantJanus;
1731           break;
1732         case 49:
1733           v = VariantSuper;
1734           break;
1735         case 50:
1736           v = VariantGreat;
1737           break;
1738         case -1:
1739           /* Found "wild" or "w" in the string but no number;
1740              must assume it's normal chess. */
1741           v = VariantNormal;
1742           break;
1743         default:
1744           sprintf(buf, _("Unknown wild type %d"), wnum);
1745           DisplayError(buf, 0);
1746           v = VariantUnknown;
1747           break;
1748         }
1749       }
1750     }
1751     if (appData.debugMode) {
1752       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1753               e, wnum, VariantName(v));
1754     }
1755     return v;
1756 }
1757
1758 static int leftover_start = 0, leftover_len = 0;
1759 char star_match[STAR_MATCH_N][MSG_SIZ];
1760
1761 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1762    advance *index beyond it, and set leftover_start to the new value of
1763    *index; else return FALSE.  If pattern contains the character '*', it
1764    matches any sequence of characters not containing '\r', '\n', or the
1765    character following the '*' (if any), and the matched sequence(s) are
1766    copied into star_match.
1767    */
1768 int
1769 looking_at(buf, index, pattern)
1770      char *buf;
1771      int *index;
1772      char *pattern;
1773 {
1774     char *bufp = &buf[*index], *patternp = pattern;
1775     int star_count = 0;
1776     char *matchp = star_match[0];
1777     
1778     for (;;) {
1779         if (*patternp == NULLCHAR) {
1780             *index = leftover_start = bufp - buf;
1781             *matchp = NULLCHAR;
1782             return TRUE;
1783         }
1784         if (*bufp == NULLCHAR) return FALSE;
1785         if (*patternp == '*') {
1786             if (*bufp == *(patternp + 1)) {
1787                 *matchp = NULLCHAR;
1788                 matchp = star_match[++star_count];
1789                 patternp += 2;
1790                 bufp++;
1791                 continue;
1792             } else if (*bufp == '\n' || *bufp == '\r') {
1793                 patternp++;
1794                 if (*patternp == NULLCHAR)
1795                   continue;
1796                 else
1797                   return FALSE;
1798             } else {
1799                 *matchp++ = *bufp++;
1800                 continue;
1801             }
1802         }
1803         if (*patternp != *bufp) return FALSE;
1804         patternp++;
1805         bufp++;
1806     }
1807 }
1808
1809 void
1810 SendToPlayer(data, length)
1811      char *data;
1812      int length;
1813 {
1814     int error, outCount;
1815     outCount = OutputToProcess(NoProc, data, length, &error);
1816     if (outCount < length) {
1817         DisplayFatalError(_("Error writing to display"), error, 1);
1818     }
1819 }
1820
1821 void
1822 PackHolding(packed, holding)
1823      char packed[];
1824      char *holding;
1825 {
1826     char *p = holding;
1827     char *q = packed;
1828     int runlength = 0;
1829     int curr = 9999;
1830     do {
1831         if (*p == curr) {
1832             runlength++;
1833         } else {
1834             switch (runlength) {
1835               case 0:
1836                 break;
1837               case 1:
1838                 *q++ = curr;
1839                 break;
1840               case 2:
1841                 *q++ = curr;
1842                 *q++ = curr;
1843                 break;
1844               default:
1845                 sprintf(q, "%d", runlength);
1846                 while (*q) q++;
1847                 *q++ = curr;
1848                 break;
1849             }
1850             runlength = 1;
1851             curr = *p;
1852         }
1853     } while (*p++);
1854     *q = NULLCHAR;
1855 }
1856
1857 /* Telnet protocol requests from the front end */
1858 void
1859 TelnetRequest(ddww, option)
1860      unsigned char ddww, option;
1861 {
1862     unsigned char msg[3];
1863     int outCount, outError;
1864
1865     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1866
1867     if (appData.debugMode) {
1868         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1869         switch (ddww) {
1870           case TN_DO:
1871             ddwwStr = "DO";
1872             break;
1873           case TN_DONT:
1874             ddwwStr = "DONT";
1875             break;
1876           case TN_WILL:
1877             ddwwStr = "WILL";
1878             break;
1879           case TN_WONT:
1880             ddwwStr = "WONT";
1881             break;
1882           default:
1883             ddwwStr = buf1;
1884             sprintf(buf1, "%d", ddww);
1885             break;
1886         }
1887         switch (option) {
1888           case TN_ECHO:
1889             optionStr = "ECHO";
1890             break;
1891           default:
1892             optionStr = buf2;
1893             sprintf(buf2, "%d", option);
1894             break;
1895         }
1896         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1897     }
1898     msg[0] = TN_IAC;
1899     msg[1] = ddww;
1900     msg[2] = option;
1901     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1902     if (outCount < 3) {
1903         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1904     }
1905 }
1906
1907 void
1908 DoEcho()
1909 {
1910     if (!appData.icsActive) return;
1911     TelnetRequest(TN_DO, TN_ECHO);
1912 }
1913
1914 void
1915 DontEcho()
1916 {
1917     if (!appData.icsActive) return;
1918     TelnetRequest(TN_DONT, TN_ECHO);
1919 }
1920
1921 void
1922 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1923 {
1924     /* put the holdings sent to us by the server on the board holdings area */
1925     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1926     char p;
1927     ChessSquare piece;
1928
1929     if(gameInfo.holdingsWidth < 2)  return;
1930
1931     if( (int)lowestPiece >= BlackPawn ) {
1932         holdingsColumn = 0;
1933         countsColumn = 1;
1934         holdingsStartRow = BOARD_HEIGHT-1;
1935         direction = -1;
1936     } else {
1937         holdingsColumn = BOARD_WIDTH-1;
1938         countsColumn = BOARD_WIDTH-2;
1939         holdingsStartRow = 0;
1940         direction = 1;
1941     }
1942
1943     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1944         board[i][holdingsColumn] = EmptySquare;
1945         board[i][countsColumn]   = (ChessSquare) 0;
1946     }
1947     while( (p=*holdings++) != NULLCHAR ) {
1948         piece = CharToPiece( ToUpper(p) );
1949         if(piece == EmptySquare) continue;
1950         /*j = (int) piece - (int) WhitePawn;*/
1951         j = PieceToNumber(piece);
1952         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1953         if(j < 0) continue;               /* should not happen */
1954         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1955         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1956         board[holdingsStartRow+j*direction][countsColumn]++;
1957     }
1958
1959 }
1960
1961
1962 void
1963 VariantSwitch(Board board, VariantClass newVariant)
1964 {
1965    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1966    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1967 //   Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1968
1969    startedFromPositionFile = FALSE;
1970    if(gameInfo.variant == newVariant) return;
1971
1972    /* [HGM] This routine is called each time an assignment is made to
1973     * gameInfo.variant during a game, to make sure the board sizes
1974     * are set to match the new variant. If that means adding or deleting
1975     * holdings, we shift the playing board accordingly
1976     * This kludge is needed because in ICS observe mode, we get boards
1977     * of an ongoing game without knowing the variant, and learn about the
1978     * latter only later. This can be because of the move list we requested,
1979     * in which case the game history is refilled from the beginning anyway,
1980     * but also when receiving holdings of a crazyhouse game. In the latter
1981     * case we want to add those holdings to the already received position.
1982     */
1983
1984
1985   if (appData.debugMode) {
1986     fprintf(debugFP, "Switch board from %s to %s\n",
1987                VariantName(gameInfo.variant), VariantName(newVariant));
1988     setbuf(debugFP, NULL);
1989   }
1990     shuffleOpenings = 0;       /* [HGM] shuffle */
1991     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1992     switch(newVariant) {
1993             case VariantShogi:
1994               newWidth = 9;  newHeight = 9;
1995               gameInfo.holdingsSize = 7;
1996             case VariantBughouse:
1997             case VariantCrazyhouse:
1998               newHoldingsWidth = 2; break;
1999             default:
2000               newHoldingsWidth = gameInfo.holdingsSize = 0;
2001     }
2002
2003     if(newWidth  != gameInfo.boardWidth  ||
2004        newHeight != gameInfo.boardHeight ||
2005        newHoldingsWidth != gameInfo.holdingsWidth ) {
2006
2007         /* shift position to new playing area, if needed */
2008         if(newHoldingsWidth > gameInfo.holdingsWidth) {
2009            for(i=0; i<BOARD_HEIGHT; i++) 
2010                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2011                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2012                                                      board[i][j];
2013            for(i=0; i<newHeight; i++) {
2014                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2015                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2016            }
2017         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2018            for(i=0; i<BOARD_HEIGHT; i++)
2019                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2020                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2021                                                  board[i][j];
2022         }
2023
2024         gameInfo.boardWidth  = newWidth;
2025         gameInfo.boardHeight = newHeight;
2026         gameInfo.holdingsWidth = newHoldingsWidth;
2027         gameInfo.variant = newVariant;
2028         InitDrawingSizes(-2, 0);
2029
2030         /* [HGM] The following should definitely be solved in a better way */
2031 #if 0
2032         CopyBoard(board, tempBoard); /* save position in case it is board[0] */
2033         for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
2034         saveEP = epStatus[0];
2035 #endif
2036         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2037 #if 0
2038         epStatus[0] = saveEP;
2039         for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
2040         CopyBoard(tempBoard, board); /* restore position received from ICS   */
2041 #endif
2042     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2043
2044     forwardMostMove = oldForwardMostMove;
2045     backwardMostMove = oldBackwardMostMove;
2046     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2047 }
2048
2049 static int loggedOn = FALSE;
2050
2051 /*-- Game start info cache: --*/
2052 int gs_gamenum;
2053 char gs_kind[MSG_SIZ];
2054 static char player1Name[128] = "";
2055 static char player2Name[128] = "";
2056 static int player1Rating = -1;
2057 static int player2Rating = -1;
2058 /*----------------------------*/
2059
2060 ColorClass curColor = ColorNormal;
2061 int suppressKibitz = 0;
2062
2063 void
2064 read_from_ics(isr, closure, data, count, error)
2065      InputSourceRef isr;
2066      VOIDSTAR closure;
2067      char *data;
2068      int count;
2069      int error;
2070 {
2071 #define BUF_SIZE 8192
2072 #define STARTED_NONE 0
2073 #define STARTED_MOVES 1
2074 #define STARTED_BOARD 2
2075 #define STARTED_OBSERVE 3
2076 #define STARTED_HOLDINGS 4
2077 #define STARTED_CHATTER 5
2078 #define STARTED_COMMENT 6
2079 #define STARTED_MOVES_NOHIDE 7
2080     
2081     static int started = STARTED_NONE;
2082     static char parse[20000];
2083     static int parse_pos = 0;
2084     static char buf[BUF_SIZE + 1];
2085     static int firstTime = TRUE, intfSet = FALSE;
2086     static ColorClass prevColor = ColorNormal;
2087     static int savingComment = FALSE;
2088     char str[500];
2089     int i, oldi;
2090     int buf_len;
2091     int next_out;
2092     int tkind;
2093     int backup;    /* [DM] For zippy color lines */
2094     char *p;
2095     char talker[MSG_SIZ]; // [HGM] chat
2096
2097     if (appData.debugMode) {
2098       if (!error) {
2099         fprintf(debugFP, "<ICS: ");
2100         show_bytes(debugFP, data, count);
2101         fprintf(debugFP, "\n");
2102       }
2103     }
2104
2105     if (appData.debugMode) { int f = forwardMostMove;
2106         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2107                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2108     }
2109     if (count > 0) {
2110         /* If last read ended with a partial line that we couldn't parse,
2111            prepend it to the new read and try again. */
2112         if (leftover_len > 0) {
2113             for (i=0; i<leftover_len; i++)
2114               buf[i] = buf[leftover_start + i];
2115         }
2116
2117         /* Copy in new characters, removing nulls and \r's */
2118         buf_len = leftover_len;
2119         for (i = 0; i < count; i++) {
2120             if (data[i] != NULLCHAR && data[i] != '\r')
2121               buf[buf_len++] = data[i];
2122             if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && 
2123                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') {
2124                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2125                 buf[buf_len++] = ' '; // replace by space (assumes ICS does not break lines within word)
2126             }
2127         }
2128
2129         buf[buf_len] = NULLCHAR;
2130         next_out = leftover_len;
2131         leftover_start = 0;
2132         
2133         i = 0;
2134         while (i < buf_len) {
2135             /* Deal with part of the TELNET option negotiation
2136                protocol.  We refuse to do anything beyond the
2137                defaults, except that we allow the WILL ECHO option,
2138                which ICS uses to turn off password echoing when we are
2139                directly connected to it.  We reject this option
2140                if localLineEditing mode is on (always on in xboard)
2141                and we are talking to port 23, which might be a real
2142                telnet server that will try to keep WILL ECHO on permanently.
2143              */
2144             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2145                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2146                 unsigned char option;
2147                 oldi = i;
2148                 switch ((unsigned char) buf[++i]) {
2149                   case TN_WILL:
2150                     if (appData.debugMode)
2151                       fprintf(debugFP, "\n<WILL ");
2152                     switch (option = (unsigned char) buf[++i]) {
2153                       case TN_ECHO:
2154                         if (appData.debugMode)
2155                           fprintf(debugFP, "ECHO ");
2156                         /* Reply only if this is a change, according
2157                            to the protocol rules. */
2158                         if (remoteEchoOption) break;
2159                         if (appData.localLineEditing &&
2160                             atoi(appData.icsPort) == TN_PORT) {
2161                             TelnetRequest(TN_DONT, TN_ECHO);
2162                         } else {
2163                             EchoOff();
2164                             TelnetRequest(TN_DO, TN_ECHO);
2165                             remoteEchoOption = TRUE;
2166                         }
2167                         break;
2168                       default:
2169                         if (appData.debugMode)
2170                           fprintf(debugFP, "%d ", option);
2171                         /* Whatever this is, we don't want it. */
2172                         TelnetRequest(TN_DONT, option);
2173                         break;
2174                     }
2175                     break;
2176                   case TN_WONT:
2177                     if (appData.debugMode)
2178                       fprintf(debugFP, "\n<WONT ");
2179                     switch (option = (unsigned char) buf[++i]) {
2180                       case TN_ECHO:
2181                         if (appData.debugMode)
2182                           fprintf(debugFP, "ECHO ");
2183                         /* Reply only if this is a change, according
2184                            to the protocol rules. */
2185                         if (!remoteEchoOption) break;
2186                         EchoOn();
2187                         TelnetRequest(TN_DONT, TN_ECHO);
2188                         remoteEchoOption = FALSE;
2189                         break;
2190                       default:
2191                         if (appData.debugMode)
2192                           fprintf(debugFP, "%d ", (unsigned char) option);
2193                         /* Whatever this is, it must already be turned
2194                            off, because we never agree to turn on
2195                            anything non-default, so according to the
2196                            protocol rules, we don't reply. */
2197                         break;
2198                     }
2199                     break;
2200                   case TN_DO:
2201                     if (appData.debugMode)
2202                       fprintf(debugFP, "\n<DO ");
2203                     switch (option = (unsigned char) buf[++i]) {
2204                       default:
2205                         /* Whatever this is, we refuse to do it. */
2206                         if (appData.debugMode)
2207                           fprintf(debugFP, "%d ", option);
2208                         TelnetRequest(TN_WONT, option);
2209                         break;
2210                     }
2211                     break;
2212                   case TN_DONT:
2213                     if (appData.debugMode)
2214                       fprintf(debugFP, "\n<DONT ");
2215                     switch (option = (unsigned char) buf[++i]) {
2216                       default:
2217                         if (appData.debugMode)
2218                           fprintf(debugFP, "%d ", option);
2219                         /* Whatever this is, we are already not doing
2220                            it, because we never agree to do anything
2221                            non-default, so according to the protocol
2222                            rules, we don't reply. */
2223                         break;
2224                     }
2225                     break;
2226                   case TN_IAC:
2227                     if (appData.debugMode)
2228                       fprintf(debugFP, "\n<IAC ");
2229                     /* Doubled IAC; pass it through */
2230                     i--;
2231                     break;
2232                   default:
2233                     if (appData.debugMode)
2234                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2235                     /* Drop all other telnet commands on the floor */
2236                     break;
2237                 }
2238                 if (oldi > next_out)
2239                   SendToPlayer(&buf[next_out], oldi - next_out);
2240                 if (++i > next_out)
2241                   next_out = i;
2242                 continue;
2243             }
2244                 
2245             /* OK, this at least will *usually* work */
2246             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2247                 loggedOn = TRUE;
2248             }
2249             
2250             if (loggedOn && !intfSet) {
2251                 if (ics_type == ICS_ICC) {
2252                   sprintf(str,
2253                           "/set-quietly interface %s\n/set-quietly style 12\n",
2254                           programVersion);
2255
2256                 } else if (ics_type == ICS_CHESSNET) {
2257                   sprintf(str, "/style 12\n");
2258                 } else {
2259                   strcpy(str, "alias $ @\n$set interface ");
2260                   strcat(str, programVersion);
2261                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2262 #ifdef WIN32
2263                   strcat(str, "$iset nohighlight 1\n");
2264 #endif
2265                   strcat(str, "$iset lock 1\n$style 12\n");
2266                 }
2267                 SendToICS(str);
2268                 intfSet = TRUE;
2269             }
2270
2271             if (started == STARTED_COMMENT) {
2272                 /* Accumulate characters in comment */
2273                 parse[parse_pos++] = buf[i];
2274                 if (buf[i] == '\n') {
2275                     parse[parse_pos] = NULLCHAR;
2276                     if(chattingPartner>=0) {
2277                         char mess[MSG_SIZ];
2278                         sprintf(mess, "%s%s", talker, parse);
2279                         OutputChatMessage(chattingPartner, mess);
2280                         chattingPartner = -1;
2281                     } else
2282                     if(!suppressKibitz) // [HGM] kibitz
2283                         AppendComment(forwardMostMove, StripHighlight(parse));
2284                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2285                         int nrDigit = 0, nrAlph = 0, i;
2286                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2287                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2288                         parse[parse_pos] = NULLCHAR;
2289                         // try to be smart: if it does not look like search info, it should go to
2290                         // ICS interaction window after all, not to engine-output window.
2291                         for(i=0; i<parse_pos; i++) { // count letters and digits
2292                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2293                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2294                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2295                         }
2296                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2297                             int depth=0; float score;
2298                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2299                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2300                                 pvInfoList[forwardMostMove-1].depth = depth;
2301                                 pvInfoList[forwardMostMove-1].score = 100*score;
2302                             }
2303                             OutputKibitz(suppressKibitz, parse);
2304                         } else {
2305                             char tmp[MSG_SIZ];
2306                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2307                             SendToPlayer(tmp, strlen(tmp));
2308                         }
2309                     }
2310                     started = STARTED_NONE;
2311                 } else {
2312                     /* Don't match patterns against characters in chatter */
2313                     i++;
2314                     continue;
2315                 }
2316             }
2317             if (started == STARTED_CHATTER) {
2318                 if (buf[i] != '\n') {
2319                     /* Don't match patterns against characters in chatter */
2320                     i++;
2321                     continue;
2322                 }
2323                 started = STARTED_NONE;
2324             }
2325
2326             /* Kludge to deal with rcmd protocol */
2327             if (firstTime && looking_at(buf, &i, "\001*")) {
2328                 DisplayFatalError(&buf[1], 0, 1);
2329                 continue;
2330             } else {
2331                 firstTime = FALSE;
2332             }
2333
2334             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2335                 ics_type = ICS_ICC;
2336                 ics_prefix = "/";
2337                 if (appData.debugMode)
2338                   fprintf(debugFP, "ics_type %d\n", ics_type);
2339                 continue;
2340             }
2341             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2342                 ics_type = ICS_FICS;
2343                 ics_prefix = "$";
2344                 if (appData.debugMode)
2345                   fprintf(debugFP, "ics_type %d\n", ics_type);
2346                 continue;
2347             }
2348             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2349                 ics_type = ICS_CHESSNET;
2350                 ics_prefix = "/";
2351                 if (appData.debugMode)
2352                   fprintf(debugFP, "ics_type %d\n", ics_type);
2353                 continue;
2354             }
2355
2356             if (!loggedOn &&
2357                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2358                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2359                  looking_at(buf, &i, "will be \"*\""))) {
2360               strcpy(ics_handle, star_match[0]);
2361               continue;
2362             }
2363
2364             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2365               char buf[MSG_SIZ];
2366               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2367               DisplayIcsInteractionTitle(buf);
2368               have_set_title = TRUE;
2369             }
2370
2371             /* skip finger notes */
2372             if (started == STARTED_NONE &&
2373                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2374                  (buf[i] == '1' && buf[i+1] == '0')) &&
2375                 buf[i+2] == ':' && buf[i+3] == ' ') {
2376               started = STARTED_CHATTER;
2377               i += 3;
2378               continue;
2379             }
2380
2381             /* skip formula vars */
2382             if (started == STARTED_NONE &&
2383                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2384               started = STARTED_CHATTER;
2385               i += 3;
2386               continue;
2387             }
2388
2389             oldi = i;
2390             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2391             if (appData.autoKibitz && started == STARTED_NONE && 
2392                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2393                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2394                 if(looking_at(buf, &i, "* kibitzes: ") &&
2395                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2396                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2397                         suppressKibitz = TRUE;
2398                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2399                                 && (gameMode == IcsPlayingWhite)) ||
2400                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2401                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2402                             started = STARTED_CHATTER; // own kibitz we simply discard
2403                         else {
2404                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2405                             parse_pos = 0; parse[0] = NULLCHAR;
2406                             savingComment = TRUE;
2407                             suppressKibitz = gameMode != IcsObserving ? 2 :
2408                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2409                         } 
2410                         continue;
2411                 } else
2412                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2413                     started = STARTED_CHATTER;
2414                     suppressKibitz = TRUE;
2415                 }
2416             } // [HGM] kibitz: end of patch
2417
2418 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2419
2420             // [HGM] chat: intercept tells by users for which we have an open chat window
2421             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2422                                            looking_at(buf, &i, "* whispers:"))) {
2423                 int p;
2424                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2425                 chattingPartner = -1;
2426                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2427                 for(p=0; p<MAX_CHAT; p++) if(!strcmp("WHISPER", chatPartner[p])) {
2428                     talker[0] = '['; strcat(talker, "]");
2429                     chattingPartner = p; break;
2430                 }
2431                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2432                 for(p=0; p<MAX_CHAT; p++) if(!strcasecmp(talker+1, chatPartner[p])) {
2433                     talker[0] = 0;
2434                     chattingPartner = p; break;
2435                 }
2436                 if(chattingPartner<0) i = oldi; else {
2437                     started = STARTED_COMMENT;
2438                     parse_pos = 0; parse[0] = NULLCHAR;
2439                     savingComment = TRUE;
2440                     suppressKibitz = TRUE;
2441                 }
2442             } // [HGM] chat: end of patch
2443
2444             if (appData.zippyTalk || appData.zippyPlay) {
2445                 /* [DM] Backup address for color zippy lines */
2446                 backup = i;
2447 #if ZIPPY
2448        #ifdef WIN32
2449                if (loggedOn == TRUE)
2450                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2451                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2452        #else
2453                 if (ZippyControl(buf, &i) ||
2454                     ZippyConverse(buf, &i) ||
2455                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2456                       loggedOn = TRUE;
2457                       if (!appData.colorize) continue;
2458                 }
2459        #endif
2460 #endif
2461             } // [DM] 'else { ' deleted
2462                 if (
2463                     /* Regular tells and says */
2464                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2465                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2466                     looking_at(buf, &i, "* says: ") ||
2467                     /* Don't color "message" or "messages" output */
2468                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2469                     looking_at(buf, &i, "*. * at *:*: ") ||
2470                     looking_at(buf, &i, "--* (*:*): ") ||
2471                     /* Message notifications (same color as tells) */
2472                     looking_at(buf, &i, "* has left a message ") ||
2473                     looking_at(buf, &i, "* just sent you a message:\n") ||
2474                     /* Whispers and kibitzes */
2475                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2476                     looking_at(buf, &i, "* kibitzes: ") ||
2477                     /* Channel tells */
2478                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2479
2480                   if (tkind == 1 && strchr(star_match[0], ':')) {
2481                       /* Avoid "tells you:" spoofs in channels */
2482                      tkind = 3;
2483                   }
2484                   if (star_match[0][0] == NULLCHAR ||
2485                       strchr(star_match[0], ' ') ||
2486                       (tkind == 3 && strchr(star_match[1], ' '))) {
2487                     /* Reject bogus matches */
2488                     i = oldi;
2489                   } else {
2490                     if (appData.colorize) {
2491                       if (oldi > next_out) {
2492                         SendToPlayer(&buf[next_out], oldi - next_out);
2493                         next_out = oldi;
2494                       }
2495                       switch (tkind) {
2496                       case 1:
2497                         Colorize(ColorTell, FALSE);
2498                         curColor = ColorTell;
2499                         break;
2500                       case 2:
2501                         Colorize(ColorKibitz, FALSE);
2502                         curColor = ColorKibitz;
2503                         break;
2504                       case 3:
2505                         p = strrchr(star_match[1], '(');
2506                         if (p == NULL) {
2507                           p = star_match[1];
2508                         } else {
2509                           p++;
2510                         }
2511                         if (atoi(p) == 1) {
2512                           Colorize(ColorChannel1, FALSE);
2513                           curColor = ColorChannel1;
2514                         } else {
2515                           Colorize(ColorChannel, FALSE);
2516                           curColor = ColorChannel;
2517                         }
2518                         break;
2519                       case 5:
2520                         curColor = ColorNormal;
2521                         break;
2522                       }
2523                     }
2524                     if (started == STARTED_NONE && appData.autoComment &&
2525                         (gameMode == IcsObserving ||
2526                          gameMode == IcsPlayingWhite ||
2527                          gameMode == IcsPlayingBlack)) {
2528                       parse_pos = i - oldi;
2529                       memcpy(parse, &buf[oldi], parse_pos);
2530                       parse[parse_pos] = NULLCHAR;
2531                       started = STARTED_COMMENT;
2532                       savingComment = TRUE;
2533                     } else {
2534                       started = STARTED_CHATTER;
2535                       savingComment = FALSE;
2536                     }
2537                     loggedOn = TRUE;
2538                     continue;
2539                   }
2540                 }
2541
2542                 if (looking_at(buf, &i, "* s-shouts: ") ||
2543                     looking_at(buf, &i, "* c-shouts: ")) {
2544                     if (appData.colorize) {
2545                         if (oldi > next_out) {
2546                             SendToPlayer(&buf[next_out], oldi - next_out);
2547                             next_out = oldi;
2548                         }
2549                         Colorize(ColorSShout, FALSE);
2550                         curColor = ColorSShout;
2551                     }
2552                     loggedOn = TRUE;
2553                     started = STARTED_CHATTER;
2554                     continue;
2555                 }
2556
2557                 if (looking_at(buf, &i, "--->")) {
2558                     loggedOn = TRUE;
2559                     continue;
2560                 }
2561
2562                 if (looking_at(buf, &i, "* shouts: ") ||
2563                     looking_at(buf, &i, "--> ")) {
2564                     if (appData.colorize) {
2565                         if (oldi > next_out) {
2566                             SendToPlayer(&buf[next_out], oldi - next_out);
2567                             next_out = oldi;
2568                         }
2569                         Colorize(ColorShout, FALSE);
2570                         curColor = ColorShout;
2571                     }
2572                     loggedOn = TRUE;
2573                     started = STARTED_CHATTER;
2574                     continue;
2575                 }
2576
2577                 if (looking_at( buf, &i, "Challenge:")) {
2578                     if (appData.colorize) {
2579                         if (oldi > next_out) {
2580                             SendToPlayer(&buf[next_out], oldi - next_out);
2581                             next_out = oldi;
2582                         }
2583                         Colorize(ColorChallenge, FALSE);
2584                         curColor = ColorChallenge;
2585                     }
2586                     loggedOn = TRUE;
2587                     continue;
2588                 }
2589
2590                 if (looking_at(buf, &i, "* offers you") ||
2591                     looking_at(buf, &i, "* offers to be") ||
2592                     looking_at(buf, &i, "* would like to") ||
2593                     looking_at(buf, &i, "* requests to") ||
2594                     looking_at(buf, &i, "Your opponent offers") ||
2595                     looking_at(buf, &i, "Your opponent requests")) {
2596
2597                     if (appData.colorize) {
2598                         if (oldi > next_out) {
2599                             SendToPlayer(&buf[next_out], oldi - next_out);
2600                             next_out = oldi;
2601                         }
2602                         Colorize(ColorRequest, FALSE);
2603                         curColor = ColorRequest;
2604                     }
2605                     continue;
2606                 }
2607
2608                 if (looking_at(buf, &i, "* (*) seeking")) {
2609                     if (appData.colorize) {
2610                         if (oldi > next_out) {
2611                             SendToPlayer(&buf[next_out], oldi - next_out);
2612                             next_out = oldi;
2613                         }
2614                         Colorize(ColorSeek, FALSE);
2615                         curColor = ColorSeek;
2616                     }
2617                     continue;
2618             }
2619
2620             if (looking_at(buf, &i, "\\   ")) {
2621                 if (prevColor != ColorNormal) {
2622                     if (oldi > next_out) {
2623                         SendToPlayer(&buf[next_out], oldi - next_out);
2624                         next_out = oldi;
2625                     }
2626                     Colorize(prevColor, TRUE);
2627                     curColor = prevColor;
2628                 }
2629                 if (savingComment) {
2630                     parse_pos = i - oldi;
2631                     memcpy(parse, &buf[oldi], parse_pos);
2632                     parse[parse_pos] = NULLCHAR;
2633                     started = STARTED_COMMENT;
2634                 } else {
2635                     started = STARTED_CHATTER;
2636                 }
2637                 continue;
2638             }
2639
2640             if (looking_at(buf, &i, "Black Strength :") ||
2641                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2642                 looking_at(buf, &i, "<10>") ||
2643                 looking_at(buf, &i, "#@#")) {
2644                 /* Wrong board style */
2645                 loggedOn = TRUE;
2646                 SendToICS(ics_prefix);
2647                 SendToICS("set style 12\n");
2648                 SendToICS(ics_prefix);
2649                 SendToICS("refresh\n");
2650                 continue;
2651             }
2652             
2653             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2654                 ICSInitScript();
2655                 have_sent_ICS_logon = 1;
2656                 continue;
2657             }
2658               
2659             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2660                 (looking_at(buf, &i, "\n<12> ") ||
2661                  looking_at(buf, &i, "<12> "))) {
2662                 loggedOn = TRUE;
2663                 if (oldi > next_out) {
2664                     SendToPlayer(&buf[next_out], oldi - next_out);
2665                 }
2666                 next_out = i;
2667                 started = STARTED_BOARD;
2668                 parse_pos = 0;
2669                 continue;
2670             }
2671
2672             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2673                 looking_at(buf, &i, "<b1> ")) {
2674                 if (oldi > next_out) {
2675                     SendToPlayer(&buf[next_out], oldi - next_out);
2676                 }
2677                 next_out = i;
2678                 started = STARTED_HOLDINGS;
2679                 parse_pos = 0;
2680                 continue;
2681             }
2682
2683             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2684                 loggedOn = TRUE;
2685                 /* Header for a move list -- first line */
2686
2687                 switch (ics_getting_history) {
2688                   case H_FALSE:
2689                     switch (gameMode) {
2690                       case IcsIdle:
2691                       case BeginningOfGame:
2692                         /* User typed "moves" or "oldmoves" while we
2693                            were idle.  Pretend we asked for these
2694                            moves and soak them up so user can step
2695                            through them and/or save them.
2696                            */
2697                         Reset(FALSE, TRUE);
2698                         gameMode = IcsObserving;
2699                         ModeHighlight();
2700                         ics_gamenum = -1;
2701                         ics_getting_history = H_GOT_UNREQ_HEADER;
2702                         break;
2703                       case EditGame: /*?*/
2704                       case EditPosition: /*?*/
2705                         /* Should above feature work in these modes too? */
2706                         /* For now it doesn't */
2707                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2708                         break;
2709                       default:
2710                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2711                         break;
2712                     }
2713                     break;
2714                   case H_REQUESTED:
2715                     /* Is this the right one? */
2716                     if (gameInfo.white && gameInfo.black &&
2717                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2718                         strcmp(gameInfo.black, star_match[2]) == 0) {
2719                         /* All is well */
2720                         ics_getting_history = H_GOT_REQ_HEADER;
2721                     }
2722                     break;
2723                   case H_GOT_REQ_HEADER:
2724                   case H_GOT_UNREQ_HEADER:
2725                   case H_GOT_UNWANTED_HEADER:
2726                   case H_GETTING_MOVES:
2727                     /* Should not happen */
2728                     DisplayError(_("Error gathering move list: two headers"), 0);
2729                     ics_getting_history = H_FALSE;
2730                     break;
2731                 }
2732
2733                 /* Save player ratings into gameInfo if needed */
2734                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2735                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2736                     (gameInfo.whiteRating == -1 ||
2737                      gameInfo.blackRating == -1)) {
2738
2739                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2740                     gameInfo.blackRating = string_to_rating(star_match[3]);
2741                     if (appData.debugMode)
2742                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2743                               gameInfo.whiteRating, gameInfo.blackRating);
2744                 }
2745                 continue;
2746             }
2747
2748             if (looking_at(buf, &i,
2749               "* * match, initial time: * minute*, increment: * second")) {
2750                 /* Header for a move list -- second line */
2751                 /* Initial board will follow if this is a wild game */
2752                 if (gameInfo.event != NULL) free(gameInfo.event);
2753                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2754                 gameInfo.event = StrSave(str);
2755                 /* [HGM] we switched variant. Translate boards if needed. */
2756                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2757                 continue;
2758             }
2759
2760             if (looking_at(buf, &i, "Move  ")) {
2761                 /* Beginning of a move list */
2762                 switch (ics_getting_history) {
2763                   case H_FALSE:
2764                     /* Normally should not happen */
2765                     /* Maybe user hit reset while we were parsing */
2766                     break;
2767                   case H_REQUESTED:
2768                     /* Happens if we are ignoring a move list that is not
2769                      * the one we just requested.  Common if the user
2770                      * tries to observe two games without turning off
2771                      * getMoveList */
2772                     break;
2773                   case H_GETTING_MOVES:
2774                     /* Should not happen */
2775                     DisplayError(_("Error gathering move list: nested"), 0);
2776                     ics_getting_history = H_FALSE;
2777                     break;
2778                   case H_GOT_REQ_HEADER:
2779                     ics_getting_history = H_GETTING_MOVES;
2780                     started = STARTED_MOVES;
2781                     parse_pos = 0;
2782                     if (oldi > next_out) {
2783                         SendToPlayer(&buf[next_out], oldi - next_out);
2784                     }
2785                     break;
2786                   case H_GOT_UNREQ_HEADER:
2787                     ics_getting_history = H_GETTING_MOVES;
2788                     started = STARTED_MOVES_NOHIDE;
2789                     parse_pos = 0;
2790                     break;
2791                   case H_GOT_UNWANTED_HEADER:
2792                     ics_getting_history = H_FALSE;
2793                     break;
2794                 }
2795                 continue;
2796             }                           
2797             
2798             if (looking_at(buf, &i, "% ") ||
2799                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2800                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2801                 savingComment = FALSE;
2802                 switch (started) {
2803                   case STARTED_MOVES:
2804                   case STARTED_MOVES_NOHIDE:
2805                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2806                     parse[parse_pos + i - oldi] = NULLCHAR;
2807                     ParseGameHistory(parse);
2808 #if ZIPPY
2809                     if (appData.zippyPlay && first.initDone) {
2810                         FeedMovesToProgram(&first, forwardMostMove);
2811                         if (gameMode == IcsPlayingWhite) {
2812                             if (WhiteOnMove(forwardMostMove)) {
2813                                 if (first.sendTime) {
2814                                   if (first.useColors) {
2815                                     SendToProgram("black\n", &first); 
2816                                   }
2817                                   SendTimeRemaining(&first, TRUE);
2818                                 }
2819 #if 0
2820                                 if (first.useColors) {
2821                                   SendToProgram("white\ngo\n", &first);
2822                                 } else {
2823                                   SendToProgram("go\n", &first);
2824                                 }
2825 #else
2826                                 if (first.useColors) {
2827                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2828                                 }
2829                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2830 #endif
2831                                 first.maybeThinking = TRUE;
2832                             } else {
2833                                 if (first.usePlayother) {
2834                                   if (first.sendTime) {
2835                                     SendTimeRemaining(&first, TRUE);
2836                                   }
2837                                   SendToProgram("playother\n", &first);
2838                                   firstMove = FALSE;
2839                                 } else {
2840                                   firstMove = TRUE;
2841                                 }
2842                             }
2843                         } else if (gameMode == IcsPlayingBlack) {
2844                             if (!WhiteOnMove(forwardMostMove)) {
2845                                 if (first.sendTime) {
2846                                   if (first.useColors) {
2847                                     SendToProgram("white\n", &first);
2848                                   }
2849                                   SendTimeRemaining(&first, FALSE);
2850                                 }
2851 #if 0
2852                                 if (first.useColors) {
2853                                   SendToProgram("black\ngo\n", &first);
2854                                 } else {
2855                                   SendToProgram("go\n", &first);
2856                                 }
2857 #else
2858                                 if (first.useColors) {
2859                                   SendToProgram("black\n", &first);
2860                                 }
2861                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2862 #endif
2863                                 first.maybeThinking = TRUE;
2864                             } else {
2865                                 if (first.usePlayother) {
2866                                   if (first.sendTime) {
2867                                     SendTimeRemaining(&first, FALSE);
2868                                   }
2869                                   SendToProgram("playother\n", &first);
2870                                   firstMove = FALSE;
2871                                 } else {
2872                                   firstMove = TRUE;
2873                                 }
2874                             }
2875                         }                       
2876                     }
2877 #endif
2878                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2879                         /* Moves came from oldmoves or moves command
2880                            while we weren't doing anything else.
2881                            */
2882                         currentMove = forwardMostMove;
2883                         ClearHighlights();/*!!could figure this out*/
2884                         flipView = appData.flipView;
2885                         DrawPosition(FALSE, boards[currentMove]);
2886                         DisplayBothClocks();
2887                         sprintf(str, "%s vs. %s",
2888                                 gameInfo.white, gameInfo.black);
2889                         DisplayTitle(str);
2890                         gameMode = IcsIdle;
2891                     } else {
2892                         /* Moves were history of an active game */
2893                         if (gameInfo.resultDetails != NULL) {
2894                             free(gameInfo.resultDetails);
2895                             gameInfo.resultDetails = NULL;
2896                         }
2897                     }
2898                     HistorySet(parseList, backwardMostMove,
2899                                forwardMostMove, currentMove-1);
2900                     DisplayMove(currentMove - 1);
2901                     if (started == STARTED_MOVES) next_out = i;
2902                     started = STARTED_NONE;
2903                     ics_getting_history = H_FALSE;
2904                     break;
2905
2906                   case STARTED_OBSERVE:
2907                     started = STARTED_NONE;
2908                     SendToICS(ics_prefix);
2909                     SendToICS("refresh\n");
2910                     break;
2911
2912                   default:
2913                     break;
2914                 }
2915                 if(bookHit) { // [HGM] book: simulate book reply
2916                     static char bookMove[MSG_SIZ]; // a bit generous?
2917
2918                     programStats.nodes = programStats.depth = programStats.time = 
2919                     programStats.score = programStats.got_only_move = 0;
2920                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2921
2922                     strcpy(bookMove, "move ");
2923                     strcat(bookMove, bookHit);
2924                     HandleMachineMove(bookMove, &first);
2925                 }
2926                 continue;
2927             }
2928             
2929             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2930                  started == STARTED_HOLDINGS ||
2931                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2932                 /* Accumulate characters in move list or board */
2933                 parse[parse_pos++] = buf[i];
2934             }
2935             
2936             /* Start of game messages.  Mostly we detect start of game
2937                when the first board image arrives.  On some versions
2938                of the ICS, though, we need to do a "refresh" after starting
2939                to observe in order to get the current board right away. */
2940             if (looking_at(buf, &i, "Adding game * to observation list")) {
2941                 started = STARTED_OBSERVE;
2942                 continue;
2943             }
2944
2945             /* Handle auto-observe */
2946             if (appData.autoObserve &&
2947                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2948                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2949                 char *player;
2950                 /* Choose the player that was highlighted, if any. */
2951                 if (star_match[0][0] == '\033' ||
2952                     star_match[1][0] != '\033') {
2953                     player = star_match[0];
2954                 } else {
2955                     player = star_match[2];
2956                 }
2957                 sprintf(str, "%sobserve %s\n",
2958                         ics_prefix, StripHighlightAndTitle(player));
2959                 SendToICS(str);
2960
2961                 /* Save ratings from notify string */
2962                 strcpy(player1Name, star_match[0]);
2963                 player1Rating = string_to_rating(star_match[1]);
2964                 strcpy(player2Name, star_match[2]);
2965                 player2Rating = string_to_rating(star_match[3]);
2966
2967                 if (appData.debugMode)
2968                   fprintf(debugFP, 
2969                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2970                           player1Name, player1Rating,
2971                           player2Name, player2Rating);
2972
2973                 continue;
2974             }
2975
2976             /* Deal with automatic examine mode after a game,
2977                and with IcsObserving -> IcsExamining transition */
2978             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2979                 looking_at(buf, &i, "has made you an examiner of game *")) {
2980
2981                 int gamenum = atoi(star_match[0]);
2982                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2983                     gamenum == ics_gamenum) {
2984                     /* We were already playing or observing this game;
2985                        no need to refetch history */
2986                     gameMode = IcsExamining;
2987                     if (pausing) {
2988                         pauseExamForwardMostMove = forwardMostMove;
2989                     } else if (currentMove < forwardMostMove) {
2990                         ForwardInner(forwardMostMove);
2991                     }
2992                 } else {
2993                     /* I don't think this case really can happen */
2994                     SendToICS(ics_prefix);
2995                     SendToICS("refresh\n");
2996                 }
2997                 continue;
2998             }    
2999             
3000             /* Error messages */
3001 //          if (ics_user_moved) {
3002             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3003                 if (looking_at(buf, &i, "Illegal move") ||
3004                     looking_at(buf, &i, "Not a legal move") ||
3005                     looking_at(buf, &i, "Your king is in check") ||
3006                     looking_at(buf, &i, "It isn't your turn") ||
3007                     looking_at(buf, &i, "It is not your move")) {
3008                     /* Illegal move */
3009                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3010                         currentMove = --forwardMostMove;
3011                         DisplayMove(currentMove - 1); /* before DMError */
3012                         DrawPosition(FALSE, boards[currentMove]);
3013                         SwitchClocks();
3014                         DisplayBothClocks();
3015                     }
3016                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3017                     ics_user_moved = 0;
3018                     continue;
3019                 }
3020             }
3021
3022             if (looking_at(buf, &i, "still have time") ||
3023                 looking_at(buf, &i, "not out of time") ||
3024                 looking_at(buf, &i, "either player is out of time") ||
3025                 looking_at(buf, &i, "has timeseal; checking")) {
3026                 /* We must have called his flag a little too soon */
3027                 whiteFlag = blackFlag = FALSE;
3028                 continue;
3029             }
3030
3031             if (looking_at(buf, &i, "added * seconds to") ||
3032                 looking_at(buf, &i, "seconds were added to")) {
3033                 /* Update the clocks */
3034                 SendToICS(ics_prefix);
3035                 SendToICS("refresh\n");
3036                 continue;
3037             }
3038
3039             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3040                 ics_clock_paused = TRUE;
3041                 StopClocks();
3042                 continue;
3043             }
3044
3045             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3046                 ics_clock_paused = FALSE;
3047                 StartClocks();
3048                 continue;
3049             }
3050
3051             /* Grab player ratings from the Creating: message.
3052                Note we have to check for the special case when
3053                the ICS inserts things like [white] or [black]. */
3054             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3055                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3056                 /* star_matches:
3057                    0    player 1 name (not necessarily white)
3058                    1    player 1 rating
3059                    2    empty, white, or black (IGNORED)
3060                    3    player 2 name (not necessarily black)
3061                    4    player 2 rating
3062                    
3063                    The names/ratings are sorted out when the game
3064                    actually starts (below).
3065                 */
3066                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3067                 player1Rating = string_to_rating(star_match[1]);
3068                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3069                 player2Rating = string_to_rating(star_match[4]);
3070
3071                 if (appData.debugMode)
3072                   fprintf(debugFP, 
3073                           "Ratings from 'Creating:' %s %d, %s %d\n",
3074                           player1Name, player1Rating,
3075                           player2Name, player2Rating);
3076
3077                 continue;
3078             }
3079             
3080             /* Improved generic start/end-of-game messages */
3081             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3082                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3083                 /* If tkind == 0: */
3084                 /* star_match[0] is the game number */
3085                 /*           [1] is the white player's name */
3086                 /*           [2] is the black player's name */
3087                 /* For end-of-game: */
3088                 /*           [3] is the reason for the game end */
3089                 /*           [4] is a PGN end game-token, preceded by " " */
3090                 /* For start-of-game: */
3091                 /*           [3] begins with "Creating" or "Continuing" */
3092                 /*           [4] is " *" or empty (don't care). */
3093                 int gamenum = atoi(star_match[0]);
3094                 char *whitename, *blackname, *why, *endtoken;
3095                 ChessMove endtype = (ChessMove) 0;
3096
3097                 if (tkind == 0) {
3098                   whitename = star_match[1];
3099                   blackname = star_match[2];
3100                   why = star_match[3];
3101                   endtoken = star_match[4];
3102                 } else {
3103                   whitename = star_match[1];
3104                   blackname = star_match[3];
3105                   why = star_match[5];
3106                   endtoken = star_match[6];
3107                 }
3108
3109                 /* Game start messages */
3110                 if (strncmp(why, "Creating ", 9) == 0 ||
3111                     strncmp(why, "Continuing ", 11) == 0) {
3112                     gs_gamenum = gamenum;
3113                     strcpy(gs_kind, strchr(why, ' ') + 1);
3114 #if ZIPPY
3115                     if (appData.zippyPlay) {
3116                         ZippyGameStart(whitename, blackname);
3117                     }
3118 #endif /*ZIPPY*/
3119                     continue;
3120                 }
3121
3122                 /* Game end messages */
3123                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3124                     ics_gamenum != gamenum) {
3125                     continue;
3126                 }
3127                 while (endtoken[0] == ' ') endtoken++;
3128                 switch (endtoken[0]) {
3129                   case '*':
3130                   default:
3131                     endtype = GameUnfinished;
3132                     break;
3133                   case '0':
3134                     endtype = BlackWins;
3135                     break;
3136                   case '1':
3137                     if (endtoken[1] == '/')
3138                       endtype = GameIsDrawn;
3139                     else
3140                       endtype = WhiteWins;
3141                     break;
3142                 }
3143                 GameEnds(endtype, why, GE_ICS);
3144 #if ZIPPY
3145                 if (appData.zippyPlay && first.initDone) {
3146                     ZippyGameEnd(endtype, why);
3147                     if (first.pr == NULL) {
3148                       /* Start the next process early so that we'll
3149                          be ready for the next challenge */
3150                       StartChessProgram(&first);
3151                     }
3152                     /* Send "new" early, in case this command takes
3153                        a long time to finish, so that we'll be ready
3154                        for the next challenge. */
3155                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3156                     Reset(TRUE, TRUE);
3157                 }
3158 #endif /*ZIPPY*/
3159                 continue;
3160             }
3161
3162             if (looking_at(buf, &i, "Removing game * from observation") ||
3163                 looking_at(buf, &i, "no longer observing game *") ||
3164                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3165                 if (gameMode == IcsObserving &&
3166                     atoi(star_match[0]) == ics_gamenum)
3167                   {
3168                       /* icsEngineAnalyze */
3169                       if (appData.icsEngineAnalyze) {
3170                             ExitAnalyzeMode();
3171                             ModeHighlight();
3172                       }
3173                       StopClocks();
3174                       gameMode = IcsIdle;
3175                       ics_gamenum = -1;
3176                       ics_user_moved = FALSE;
3177                   }
3178                 continue;
3179             }
3180
3181             if (looking_at(buf, &i, "no longer examining game *")) {
3182                 if (gameMode == IcsExamining &&
3183                     atoi(star_match[0]) == ics_gamenum)
3184                   {
3185                       gameMode = IcsIdle;
3186                       ics_gamenum = -1;
3187                       ics_user_moved = FALSE;
3188                   }
3189                 continue;
3190             }
3191
3192             /* Advance leftover_start past any newlines we find,
3193                so only partial lines can get reparsed */
3194             if (looking_at(buf, &i, "\n")) {
3195                 prevColor = curColor;
3196                 if (curColor != ColorNormal) {
3197                     if (oldi > next_out) {
3198                         SendToPlayer(&buf[next_out], oldi - next_out);
3199                         next_out = oldi;
3200                     }
3201                     Colorize(ColorNormal, FALSE);
3202                     curColor = ColorNormal;
3203                 }
3204                 if (started == STARTED_BOARD) {
3205                     started = STARTED_NONE;
3206                     parse[parse_pos] = NULLCHAR;
3207                     ParseBoard12(parse);
3208                     ics_user_moved = 0;
3209
3210                     /* Send premove here */
3211                     if (appData.premove) {
3212                       char str[MSG_SIZ];
3213                       if (currentMove == 0 &&
3214                           gameMode == IcsPlayingWhite &&
3215                           appData.premoveWhite) {
3216                         sprintf(str, "%s%s\n", ics_prefix,
3217                                 appData.premoveWhiteText);
3218                         if (appData.debugMode)
3219                           fprintf(debugFP, "Sending premove:\n");
3220                         SendToICS(str);
3221                       } else if (currentMove == 1 &&
3222                                  gameMode == IcsPlayingBlack &&
3223                                  appData.premoveBlack) {
3224                         sprintf(str, "%s%s\n", ics_prefix,
3225                                 appData.premoveBlackText);
3226                         if (appData.debugMode)
3227                           fprintf(debugFP, "Sending premove:\n");
3228                         SendToICS(str);
3229                       } else if (gotPremove) {
3230                         gotPremove = 0;
3231                         ClearPremoveHighlights();
3232                         if (appData.debugMode)
3233                           fprintf(debugFP, "Sending premove:\n");
3234                           UserMoveEvent(premoveFromX, premoveFromY, 
3235                                         premoveToX, premoveToY, 
3236                                         premovePromoChar);
3237                       }
3238                     }
3239
3240                     /* Usually suppress following prompt */
3241                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3242                         if (looking_at(buf, &i, "*% ")) {
3243                             savingComment = FALSE;
3244                         }
3245                     }
3246                     next_out = i;
3247                 } else if (started == STARTED_HOLDINGS) {
3248                     int gamenum;
3249                     char new_piece[MSG_SIZ];
3250                     started = STARTED_NONE;
3251                     parse[parse_pos] = NULLCHAR;
3252                     if (appData.debugMode)
3253                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3254                                                         parse, currentMove);
3255                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3256                         gamenum == ics_gamenum) {
3257                         if (gameInfo.variant == VariantNormal) {
3258                           /* [HGM] We seem to switch variant during a game!
3259                            * Presumably no holdings were displayed, so we have
3260                            * to move the position two files to the right to
3261                            * create room for them!
3262                            */
3263                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3264                           /* Get a move list just to see the header, which
3265                              will tell us whether this is really bug or zh */
3266                           if (ics_getting_history == H_FALSE) {
3267                             ics_getting_history = H_REQUESTED;
3268                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3269                             SendToICS(str);
3270                           }
3271                         }
3272                         new_piece[0] = NULLCHAR;
3273                         sscanf(parse, "game %d white [%s black [%s <- %s",
3274                                &gamenum, white_holding, black_holding,
3275                                new_piece);
3276                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3277                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3278                         /* [HGM] copy holdings to board holdings area */
3279                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3280                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3281 #if ZIPPY
3282                         if (appData.zippyPlay && first.initDone) {
3283                             ZippyHoldings(white_holding, black_holding,
3284                                           new_piece);
3285                         }
3286 #endif /*ZIPPY*/
3287                         if (tinyLayout || smallLayout) {
3288                             char wh[16], bh[16];
3289                             PackHolding(wh, white_holding);
3290                             PackHolding(bh, black_holding);
3291                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3292                                     gameInfo.white, gameInfo.black);
3293                         } else {
3294                             sprintf(str, "%s [%s] vs. %s [%s]",
3295                                     gameInfo.white, white_holding,
3296                                     gameInfo.black, black_holding);
3297                         }
3298
3299                         DrawPosition(FALSE, boards[currentMove]);
3300                         DisplayTitle(str);
3301                     }
3302                     /* Suppress following prompt */
3303                     if (looking_at(buf, &i, "*% ")) {
3304                         savingComment = FALSE;
3305                     }
3306                     next_out = i;
3307                 }
3308                 continue;
3309             }
3310
3311             i++;                /* skip unparsed character and loop back */
3312         }
3313         
3314         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3315             started != STARTED_HOLDINGS && i > next_out) {
3316             SendToPlayer(&buf[next_out], i - next_out);
3317             next_out = i;
3318         }
3319         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3320         
3321         leftover_len = buf_len - leftover_start;
3322         /* if buffer ends with something we couldn't parse,
3323            reparse it after appending the next read */
3324         
3325     } else if (count == 0) {
3326         RemoveInputSource(isr);
3327         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3328     } else {
3329         DisplayFatalError(_("Error reading from ICS"), error, 1);
3330     }
3331 }
3332
3333
3334 /* Board style 12 looks like this:
3335    
3336    <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
3337    
3338  * The "<12> " is stripped before it gets to this routine.  The two
3339  * trailing 0's (flip state and clock ticking) are later addition, and
3340  * some chess servers may not have them, or may have only the first.
3341  * Additional trailing fields may be added in the future.  
3342  */
3343
3344 #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"
3345
3346 #define RELATION_OBSERVING_PLAYED    0
3347 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3348 #define RELATION_PLAYING_MYMOVE      1
3349 #define RELATION_PLAYING_NOTMYMOVE  -1
3350 #define RELATION_EXAMINING           2
3351 #define RELATION_ISOLATED_BOARD     -3
3352 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3353
3354 void
3355 ParseBoard12(string)
3356      char *string;
3357
3358     GameMode newGameMode;
3359     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3360     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3361     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3362     char to_play, board_chars[200];
3363     char move_str[500], str[500], elapsed_time[500];
3364     char black[32], white[32];
3365     Board board;
3366     int prevMove = currentMove;
3367     int ticking = 2;
3368     ChessMove moveType;
3369     int fromX, fromY, toX, toY;
3370     char promoChar;
3371     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3372     char *bookHit = NULL; // [HGM] book
3373
3374     fromX = fromY = toX = toY = -1;
3375     
3376     newGame = FALSE;
3377
3378     if (appData.debugMode)
3379       fprintf(debugFP, _("Parsing board: %s\n"), string);
3380
3381     move_str[0] = NULLCHAR;
3382     elapsed_time[0] = NULLCHAR;
3383     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3384         int  i = 0, j;
3385         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3386             if(string[i] == ' ') { ranks++; files = 0; }
3387             else files++;
3388             i++;
3389         }
3390         for(j = 0; j <i; j++) board_chars[j] = string[j];
3391         board_chars[i] = '\0';
3392         string += i + 1;
3393     }
3394     n = sscanf(string, PATTERN, &to_play, &double_push,
3395                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3396                &gamenum, white, black, &relation, &basetime, &increment,
3397                &white_stren, &black_stren, &white_time, &black_time,
3398                &moveNum, str, elapsed_time, move_str, &ics_flip,
3399                &ticking);
3400
3401     if (n < 21) {
3402         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3403         DisplayError(str, 0);
3404         return;
3405     }
3406
3407     /* Convert the move number to internal form */
3408     moveNum = (moveNum - 1) * 2;
3409     if (to_play == 'B') moveNum++;
3410     if (moveNum >= MAX_MOVES) {
3411       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3412                         0, 1);
3413       return;
3414     }
3415     
3416     switch (relation) {
3417       case RELATION_OBSERVING_PLAYED:
3418       case RELATION_OBSERVING_STATIC:
3419         if (gamenum == -1) {
3420             /* Old ICC buglet */
3421             relation = RELATION_OBSERVING_STATIC;
3422         }
3423         newGameMode = IcsObserving;
3424         break;
3425       case RELATION_PLAYING_MYMOVE:
3426       case RELATION_PLAYING_NOTMYMOVE:
3427         newGameMode =
3428           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3429             IcsPlayingWhite : IcsPlayingBlack;
3430         break;
3431       case RELATION_EXAMINING:
3432         newGameMode = IcsExamining;
3433         break;
3434       case RELATION_ISOLATED_BOARD:
3435       default:
3436         /* Just display this board.  If user was doing something else,
3437            we will forget about it until the next board comes. */ 
3438         newGameMode = IcsIdle;
3439         break;
3440       case RELATION_STARTING_POSITION:
3441         newGameMode = gameMode;
3442         break;
3443     }
3444     
3445     /* Modify behavior for initial board display on move listing
3446        of wild games.
3447        */
3448     switch (ics_getting_history) {
3449       case H_FALSE:
3450       case H_REQUESTED:
3451         break;
3452       case H_GOT_REQ_HEADER:
3453       case H_GOT_UNREQ_HEADER:
3454         /* This is the initial position of the current game */
3455         gamenum = ics_gamenum;
3456         moveNum = 0;            /* old ICS bug workaround */
3457         if (to_play == 'B') {
3458           startedFromSetupPosition = TRUE;
3459           blackPlaysFirst = TRUE;
3460           moveNum = 1;
3461           if (forwardMostMove == 0) forwardMostMove = 1;
3462           if (backwardMostMove == 0) backwardMostMove = 1;
3463           if (currentMove == 0) currentMove = 1;
3464         }
3465         newGameMode = gameMode;
3466         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3467         break;
3468       case H_GOT_UNWANTED_HEADER:
3469         /* This is an initial board that we don't want */
3470         return;
3471       case H_GETTING_MOVES:
3472         /* Should not happen */
3473         DisplayError(_("Error gathering move list: extra board"), 0);
3474         ics_getting_history = H_FALSE;
3475         return;
3476     }
3477     
3478     /* Take action if this is the first board of a new game, or of a
3479        different game than is currently being displayed.  */
3480     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3481         relation == RELATION_ISOLATED_BOARD) {
3482         
3483         /* Forget the old game and get the history (if any) of the new one */
3484         if (gameMode != BeginningOfGame) {
3485           Reset(FALSE, TRUE);
3486         }
3487         newGame = TRUE;
3488         if (appData.autoRaiseBoard) BoardToTop();
3489         prevMove = -3;
3490         if (gamenum == -1) {
3491             newGameMode = IcsIdle;
3492         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3493                    appData.getMoveList) {
3494             /* Need to get game history */
3495             ics_getting_history = H_REQUESTED;
3496             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3497             SendToICS(str);
3498         }
3499         
3500         /* Initially flip the board to have black on the bottom if playing
3501            black or if the ICS flip flag is set, but let the user change
3502            it with the Flip View button. */
3503         flipView = appData.autoFlipView ? 
3504           (newGameMode == IcsPlayingBlack) || ics_flip :
3505           appData.flipView;
3506         
3507         /* Done with values from previous mode; copy in new ones */
3508         gameMode = newGameMode;
3509         ModeHighlight();
3510         ics_gamenum = gamenum;
3511         if (gamenum == gs_gamenum) {
3512             int klen = strlen(gs_kind);
3513             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3514             sprintf(str, "ICS %s", gs_kind);
3515             gameInfo.event = StrSave(str);
3516         } else {
3517             gameInfo.event = StrSave("ICS game");
3518         }
3519         gameInfo.site = StrSave(appData.icsHost);
3520         gameInfo.date = PGNDate();
3521         gameInfo.round = StrSave("-");
3522         gameInfo.white = StrSave(white);
3523         gameInfo.black = StrSave(black);
3524         timeControl = basetime * 60 * 1000;
3525         timeControl_2 = 0;
3526         timeIncrement = increment * 1000;
3527         movesPerSession = 0;
3528         gameInfo.timeControl = TimeControlTagValue();
3529         VariantSwitch(board, StringToVariant(gameInfo.event) );
3530   if (appData.debugMode) {
3531     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3532     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3533     setbuf(debugFP, NULL);
3534   }
3535
3536         gameInfo.outOfBook = NULL;
3537         
3538         /* Do we have the ratings? */
3539         if (strcmp(player1Name, white) == 0 &&
3540             strcmp(player2Name, black) == 0) {
3541             if (appData.debugMode)
3542               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3543                       player1Rating, player2Rating);
3544             gameInfo.whiteRating = player1Rating;
3545             gameInfo.blackRating = player2Rating;
3546         } else if (strcmp(player2Name, white) == 0 &&
3547                    strcmp(player1Name, black) == 0) {
3548             if (appData.debugMode)
3549               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3550                       player2Rating, player1Rating);
3551             gameInfo.whiteRating = player2Rating;
3552             gameInfo.blackRating = player1Rating;
3553         }
3554         player1Name[0] = player2Name[0] = NULLCHAR;
3555
3556         /* Silence shouts if requested */
3557         if (appData.quietPlay &&
3558             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3559             SendToICS(ics_prefix);
3560             SendToICS("set shout 0\n");
3561         }
3562     }
3563     
3564     /* Deal with midgame name changes */
3565     if (!newGame) {
3566         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3567             if (gameInfo.white) free(gameInfo.white);
3568             gameInfo.white = StrSave(white);
3569         }
3570         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3571             if (gameInfo.black) free(gameInfo.black);
3572             gameInfo.black = StrSave(black);
3573         }
3574     }
3575     
3576     /* Throw away game result if anything actually changes in examine mode */
3577     if (gameMode == IcsExamining && !newGame) {
3578         gameInfo.result = GameUnfinished;
3579         if (gameInfo.resultDetails != NULL) {
3580             free(gameInfo.resultDetails);
3581             gameInfo.resultDetails = NULL;
3582         }
3583     }
3584     
3585     /* In pausing && IcsExamining mode, we ignore boards coming
3586        in if they are in a different variation than we are. */
3587     if (pauseExamInvalid) return;
3588     if (pausing && gameMode == IcsExamining) {
3589         if (moveNum <= pauseExamForwardMostMove) {
3590             pauseExamInvalid = TRUE;
3591             forwardMostMove = pauseExamForwardMostMove;
3592             return;
3593         }
3594     }
3595     
3596   if (appData.debugMode) {
3597     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3598   }
3599     /* Parse the board */
3600     for (k = 0; k < ranks; k++) {
3601       for (j = 0; j < files; j++)
3602         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3603       if(gameInfo.holdingsWidth > 1) {
3604            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3605            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3606       }
3607     }
3608     CopyBoard(boards[moveNum], board);
3609     if (moveNum == 0) {
3610         startedFromSetupPosition =
3611           !CompareBoards(board, initialPosition);
3612         if(startedFromSetupPosition)
3613             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3614     }
3615
3616     /* [HGM] Set castling rights. Take the outermost Rooks,
3617        to make it also work for FRC opening positions. Note that board12
3618        is really defective for later FRC positions, as it has no way to
3619        indicate which Rook can castle if they are on the same side of King.
3620        For the initial position we grant rights to the outermost Rooks,
3621        and remember thos rights, and we then copy them on positions
3622        later in an FRC game. This means WB might not recognize castlings with
3623        Rooks that have moved back to their original position as illegal,
3624        but in ICS mode that is not its job anyway.
3625     */
3626     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3627     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3628
3629         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3630             if(board[0][i] == WhiteRook) j = i;
3631         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3632         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3633             if(board[0][i] == WhiteRook) j = i;
3634         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3635         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3636             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3637         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3638         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3639             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3640         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3641
3642         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3643         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3644             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3645         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3646             if(board[BOARD_HEIGHT-1][k] == bKing)
3647                 initialRights[5] = castlingRights[moveNum][5] = k;
3648     } else { int r;
3649         r = castlingRights[moveNum][0] = initialRights[0];
3650         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3651         r = castlingRights[moveNum][1] = initialRights[1];
3652         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3653         r = castlingRights[moveNum][3] = initialRights[3];
3654         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3655         r = castlingRights[moveNum][4] = initialRights[4];
3656         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3657         /* wildcastle kludge: always assume King has rights */
3658         r = castlingRights[moveNum][2] = initialRights[2];
3659         r = castlingRights[moveNum][5] = initialRights[5];
3660     }
3661     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3662     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3663
3664     
3665     if (ics_getting_history == H_GOT_REQ_HEADER ||
3666         ics_getting_history == H_GOT_UNREQ_HEADER) {
3667         /* This was an initial position from a move list, not
3668            the current position */
3669         return;
3670     }
3671     
3672     /* Update currentMove and known move number limits */
3673     newMove = newGame || moveNum > forwardMostMove;
3674
3675     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3676     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3677         takeback = forwardMostMove - moveNum;
3678         for (i = 0; i < takeback; i++) {
3679              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3680              SendToProgram("undo\n", &first);
3681         }
3682     }
3683
3684     if (newGame) {
3685         forwardMostMove = backwardMostMove = currentMove = moveNum;
3686         if (gameMode == IcsExamining && moveNum == 0) {
3687           /* Workaround for ICS limitation: we are not told the wild
3688              type when starting to examine a game.  But if we ask for
3689              the move list, the move list header will tell us */
3690             ics_getting_history = H_REQUESTED;
3691             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3692             SendToICS(str);
3693         }
3694     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3695                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3696         forwardMostMove = moveNum;
3697         if (!pausing || currentMove > forwardMostMove)
3698           currentMove = forwardMostMove;
3699     } else {
3700         /* New part of history that is not contiguous with old part */ 
3701         if (pausing && gameMode == IcsExamining) {
3702             pauseExamInvalid = TRUE;
3703             forwardMostMove = pauseExamForwardMostMove;
3704             return;
3705         }
3706         forwardMostMove = backwardMostMove = currentMove = moveNum;
3707         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3708             ics_getting_history = H_REQUESTED;
3709             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3710             SendToICS(str);
3711         }
3712     }
3713     
3714     /* Update the clocks */
3715     if (strchr(elapsed_time, '.')) {
3716       /* Time is in ms */
3717       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3718       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3719     } else {
3720       /* Time is in seconds */
3721       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3722       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3723     }
3724       
3725
3726 #if ZIPPY
3727     if (appData.zippyPlay && newGame &&
3728         gameMode != IcsObserving && gameMode != IcsIdle &&
3729         gameMode != IcsExamining)
3730       ZippyFirstBoard(moveNum, basetime, increment);
3731 #endif
3732     
3733     /* Put the move on the move list, first converting
3734        to canonical algebraic form. */
3735     if (moveNum > 0) {
3736   if (appData.debugMode) {
3737     if (appData.debugMode) { int f = forwardMostMove;
3738         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3739                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3740     }
3741     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3742     fprintf(debugFP, "moveNum = %d\n", moveNum);
3743     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3744     setbuf(debugFP, NULL);
3745   }
3746         if (moveNum <= backwardMostMove) {
3747             /* We don't know what the board looked like before
3748                this move.  Punt. */
3749             strcpy(parseList[moveNum - 1], move_str);
3750             strcat(parseList[moveNum - 1], " ");
3751             strcat(parseList[moveNum - 1], elapsed_time);
3752             moveList[moveNum - 1][0] = NULLCHAR;
3753         } else if (strcmp(move_str, "none") == 0) {
3754             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3755             /* Again, we don't know what the board looked like;
3756                this is really the start of the game. */
3757             parseList[moveNum - 1][0] = NULLCHAR;
3758             moveList[moveNum - 1][0] = NULLCHAR;
3759             backwardMostMove = moveNum;
3760             startedFromSetupPosition = TRUE;
3761             fromX = fromY = toX = toY = -1;
3762         } else {
3763           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3764           //                 So we parse the long-algebraic move string in stead of the SAN move
3765           int valid; char buf[MSG_SIZ], *prom;
3766
3767           // str looks something like "Q/a1-a2"; kill the slash
3768           if(str[1] == '/') 
3769                 sprintf(buf, "%c%s", str[0], str+2);
3770           else  strcpy(buf, str); // might be castling
3771           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3772                 strcat(buf, prom); // long move lacks promo specification!
3773           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3774                 if(appData.debugMode) 
3775                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3776                 strcpy(move_str, buf);
3777           }
3778           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3779                                 &fromX, &fromY, &toX, &toY, &promoChar)
3780                || ParseOneMove(buf, moveNum - 1, &moveType,
3781                                 &fromX, &fromY, &toX, &toY, &promoChar);
3782           // end of long SAN patch
3783           if (valid) {
3784             (void) CoordsToAlgebraic(boards[moveNum - 1],
3785                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3786                                      fromY, fromX, toY, toX, promoChar,
3787                                      parseList[moveNum-1]);
3788             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3789                              castlingRights[moveNum]) ) {
3790               case MT_NONE:
3791               case MT_STALEMATE:
3792               default:
3793                 break;
3794               case MT_CHECK:
3795                 if(gameInfo.variant != VariantShogi)
3796                     strcat(parseList[moveNum - 1], "+");
3797                 break;
3798               case MT_CHECKMATE:
3799               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3800                 strcat(parseList[moveNum - 1], "#");
3801                 break;
3802             }
3803             strcat(parseList[moveNum - 1], " ");
3804             strcat(parseList[moveNum - 1], elapsed_time);
3805             /* currentMoveString is set as a side-effect of ParseOneMove */
3806             strcpy(moveList[moveNum - 1], currentMoveString);
3807             strcat(moveList[moveNum - 1], "\n");
3808           } else {
3809             /* Move from ICS was illegal!?  Punt. */
3810   if (appData.debugMode) {
3811     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3812     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3813   }
3814 #if 0
3815             if (appData.testLegality && appData.debugMode) {
3816                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3817                 DisplayError(str, 0);
3818             }
3819 #endif
3820             strcpy(parseList[moveNum - 1], move_str);
3821             strcat(parseList[moveNum - 1], " ");
3822             strcat(parseList[moveNum - 1], elapsed_time);
3823             moveList[moveNum - 1][0] = NULLCHAR;
3824             fromX = fromY = toX = toY = -1;
3825           }
3826         }
3827   if (appData.debugMode) {
3828     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3829     setbuf(debugFP, NULL);
3830   }
3831
3832 #if ZIPPY
3833         /* Send move to chess program (BEFORE animating it). */
3834         if (appData.zippyPlay && !newGame && newMove && 
3835            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3836
3837             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3838                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3839                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3840                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3841                             move_str);
3842                     DisplayError(str, 0);
3843                 } else {
3844                     if (first.sendTime) {
3845                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3846                     }
3847                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3848                     if (firstMove && !bookHit) {
3849                         firstMove = FALSE;
3850                         if (first.useColors) {
3851                           SendToProgram(gameMode == IcsPlayingWhite ?
3852                                         "white\ngo\n" :
3853                                         "black\ngo\n", &first);
3854                         } else {
3855                           SendToProgram("go\n", &first);
3856                         }
3857                         first.maybeThinking = TRUE;
3858                     }
3859                 }
3860             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3861               if (moveList[moveNum - 1][0] == NULLCHAR) {
3862                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3863                 DisplayError(str, 0);
3864               } else {
3865                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3866                 SendMoveToProgram(moveNum - 1, &first);
3867               }
3868             }
3869         }
3870 #endif
3871     }
3872
3873     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3874         /* If move comes from a remote source, animate it.  If it
3875            isn't remote, it will have already been animated. */
3876         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3877             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3878         }
3879         if (!pausing && appData.highlightLastMove) {
3880             SetHighlights(fromX, fromY, toX, toY);
3881         }
3882     }
3883     
3884     /* Start the clocks */
3885     whiteFlag = blackFlag = FALSE;
3886     appData.clockMode = !(basetime == 0 && increment == 0);
3887     if (ticking == 0) {
3888       ics_clock_paused = TRUE;
3889       StopClocks();
3890     } else if (ticking == 1) {
3891       ics_clock_paused = FALSE;
3892     }
3893     if (gameMode == IcsIdle ||
3894         relation == RELATION_OBSERVING_STATIC ||
3895         relation == RELATION_EXAMINING ||
3896         ics_clock_paused)
3897       DisplayBothClocks();
3898     else
3899       StartClocks();
3900     
3901     /* Display opponents and material strengths */
3902     if (gameInfo.variant != VariantBughouse &&
3903         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3904         if (tinyLayout || smallLayout) {
3905             if(gameInfo.variant == VariantNormal)
3906                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3907                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3908                     basetime, increment);
3909             else
3910                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3911                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3912                     basetime, increment, (int) gameInfo.variant);
3913         } else {
3914             if(gameInfo.variant == VariantNormal)
3915                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3916                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3917                     basetime, increment);
3918             else
3919                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3920                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3921                     basetime, increment, VariantName(gameInfo.variant));
3922         }
3923         DisplayTitle(str);
3924   if (appData.debugMode) {
3925     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3926   }
3927     }
3928
3929    
3930     /* Display the board */
3931     if (!pausing && !appData.noGUI) {
3932       
3933       if (appData.premove)
3934           if (!gotPremove || 
3935              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3936              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3937               ClearPremoveHighlights();
3938
3939       DrawPosition(FALSE, boards[currentMove]);
3940       DisplayMove(moveNum - 1);
3941       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3942             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3943               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3944         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3945       }
3946     }
3947
3948     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3949 #if ZIPPY
3950     if(bookHit) { // [HGM] book: simulate book reply
3951         static char bookMove[MSG_SIZ]; // a bit generous?
3952
3953         programStats.nodes = programStats.depth = programStats.time = 
3954         programStats.score = programStats.got_only_move = 0;
3955         sprintf(programStats.movelist, "%s (xbook)", bookHit);
3956
3957         strcpy(bookMove, "move ");
3958         strcat(bookMove, bookHit);
3959         HandleMachineMove(bookMove, &first);
3960     }
3961 #endif
3962 }
3963
3964 void
3965 GetMoveListEvent()
3966 {
3967     char buf[MSG_SIZ];
3968     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3969         ics_getting_history = H_REQUESTED;
3970         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3971         SendToICS(buf);
3972     }
3973 }
3974
3975 void
3976 AnalysisPeriodicEvent(force)
3977      int force;
3978 {
3979     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3980          && !force) || !appData.periodicUpdates)
3981       return;
3982
3983     /* Send . command to Crafty to collect stats */
3984     SendToProgram(".\n", &first);
3985
3986     /* Don't send another until we get a response (this makes
3987        us stop sending to old Crafty's which don't understand
3988        the "." command (sending illegal cmds resets node count & time,
3989        which looks bad)) */
3990     programStats.ok_to_send = 0;
3991 }
3992
3993 void
3994 SendMoveToProgram(moveNum, cps)
3995      int moveNum;
3996      ChessProgramState *cps;
3997 {
3998     char buf[MSG_SIZ];
3999
4000     if (cps->useUsermove) {
4001       SendToProgram("usermove ", cps);
4002     }
4003     if (cps->useSAN) {
4004       char *space;
4005       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4006         int len = space - parseList[moveNum];
4007         memcpy(buf, parseList[moveNum], len);
4008         buf[len++] = '\n';
4009         buf[len] = NULLCHAR;
4010       } else {
4011         sprintf(buf, "%s\n", parseList[moveNum]);
4012       }
4013       SendToProgram(buf, cps);
4014     } else {
4015       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4016         AlphaRank(moveList[moveNum], 4);
4017         SendToProgram(moveList[moveNum], cps);
4018         AlphaRank(moveList[moveNum], 4); // and back
4019       } else
4020       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4021        * the engine. It would be nice to have a better way to identify castle 
4022        * moves here. */
4023       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4024                                                                          && cps->useOOCastle) {
4025         int fromX = moveList[moveNum][0] - AAA; 
4026         int fromY = moveList[moveNum][1] - ONE;
4027         int toX = moveList[moveNum][2] - AAA; 
4028         int toY = moveList[moveNum][3] - ONE;
4029         if((boards[moveNum][fromY][fromX] == WhiteKing 
4030             && boards[moveNum][toY][toX] == WhiteRook)
4031            || (boards[moveNum][fromY][fromX] == BlackKing 
4032                && boards[moveNum][toY][toX] == BlackRook)) {
4033           if(toX > fromX) SendToProgram("O-O\n", cps);
4034           else SendToProgram("O-O-O\n", cps);
4035         }
4036         else SendToProgram(moveList[moveNum], cps);
4037       }
4038       else SendToProgram(moveList[moveNum], cps);
4039       /* End of additions by Tord */
4040     }
4041
4042     /* [HGM] setting up the opening has brought engine in force mode! */
4043     /*       Send 'go' if we are in a mode where machine should play. */
4044     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4045         (gameMode == TwoMachinesPlay   ||
4046 #ifdef ZIPPY
4047          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4048 #endif
4049          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4050         SendToProgram("go\n", cps);
4051   if (appData.debugMode) {
4052     fprintf(debugFP, "(extra)\n");
4053   }
4054     }
4055     setboardSpoiledMachineBlack = 0;
4056 }
4057
4058 void
4059 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4060      ChessMove moveType;
4061      int fromX, fromY, toX, toY;
4062 {
4063     char user_move[MSG_SIZ];
4064
4065     switch (moveType) {
4066       default:
4067         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4068                 (int)moveType, fromX, fromY, toX, toY);
4069         DisplayError(user_move + strlen("say "), 0);
4070         break;
4071       case WhiteKingSideCastle:
4072       case BlackKingSideCastle:
4073       case WhiteQueenSideCastleWild:
4074       case BlackQueenSideCastleWild:
4075       /* PUSH Fabien */
4076       case WhiteHSideCastleFR:
4077       case BlackHSideCastleFR:
4078       /* POP Fabien */
4079         sprintf(user_move, "o-o\n");
4080         break;
4081       case WhiteQueenSideCastle:
4082       case BlackQueenSideCastle:
4083       case WhiteKingSideCastleWild:
4084       case BlackKingSideCastleWild:
4085       /* PUSH Fabien */
4086       case WhiteASideCastleFR:
4087       case BlackASideCastleFR:
4088       /* POP Fabien */
4089         sprintf(user_move, "o-o-o\n");
4090         break;
4091       case WhitePromotionQueen:
4092       case BlackPromotionQueen:
4093       case WhitePromotionRook:
4094       case BlackPromotionRook:
4095       case WhitePromotionBishop:
4096       case BlackPromotionBishop:
4097       case WhitePromotionKnight:
4098       case BlackPromotionKnight:
4099       case WhitePromotionKing:
4100       case BlackPromotionKing:
4101       case WhitePromotionChancellor:
4102       case BlackPromotionChancellor:
4103       case WhitePromotionArchbishop:
4104       case BlackPromotionArchbishop:
4105         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4106             sprintf(user_move, "%c%c%c%c=%c\n",
4107                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4108                 PieceToChar(WhiteFerz));
4109         else if(gameInfo.variant == VariantGreat)
4110             sprintf(user_move, "%c%c%c%c=%c\n",
4111                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4112                 PieceToChar(WhiteMan));
4113         else
4114             sprintf(user_move, "%c%c%c%c=%c\n",
4115                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4116                 PieceToChar(PromoPiece(moveType)));
4117         break;
4118       case WhiteDrop:
4119       case BlackDrop:
4120         sprintf(user_move, "%c@%c%c\n",
4121                 ToUpper(PieceToChar((ChessSquare) fromX)),
4122                 AAA + toX, ONE + toY);
4123         break;
4124       case NormalMove:
4125       case WhiteCapturesEnPassant:
4126       case BlackCapturesEnPassant:
4127       case IllegalMove:  /* could be a variant we don't quite understand */
4128         sprintf(user_move, "%c%c%c%c\n",
4129                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4130         break;
4131     }
4132     SendToICS(user_move);
4133     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4134         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4135 }
4136
4137 void
4138 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4139      int rf, ff, rt, ft;
4140      char promoChar;
4141      char move[7];
4142 {
4143     if (rf == DROP_RANK) {
4144         sprintf(move, "%c@%c%c\n",
4145                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4146     } else {
4147         if (promoChar == 'x' || promoChar == NULLCHAR) {
4148             sprintf(move, "%c%c%c%c\n",
4149                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4150         } else {
4151             sprintf(move, "%c%c%c%c%c\n",
4152                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4153         }
4154     }
4155 }
4156
4157 void
4158 ProcessICSInitScript(f)
4159      FILE *f;
4160 {
4161     char buf[MSG_SIZ];
4162
4163     while (fgets(buf, MSG_SIZ, f)) {
4164         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4165     }
4166
4167     fclose(f);
4168 }
4169
4170
4171 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4172 void
4173 AlphaRank(char *move, int n)
4174 {
4175 //    char *p = move, c; int x, y;
4176
4177     if (appData.debugMode) {
4178         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4179     }
4180
4181     if(move[1]=='*' && 
4182        move[2]>='0' && move[2]<='9' &&
4183        move[3]>='a' && move[3]<='x'    ) {
4184         move[1] = '@';
4185         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4186         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4187     } else
4188     if(move[0]>='0' && move[0]<='9' &&
4189        move[1]>='a' && move[1]<='x' &&
4190        move[2]>='0' && move[2]<='9' &&
4191        move[3]>='a' && move[3]<='x'    ) {
4192         /* input move, Shogi -> normal */
4193         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4194         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4195         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4196         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4197     } else
4198     if(move[1]=='@' &&
4199        move[3]>='0' && move[3]<='9' &&
4200        move[2]>='a' && move[2]<='x'    ) {
4201         move[1] = '*';
4202         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4203         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4204     } else
4205     if(
4206        move[0]>='a' && move[0]<='x' &&
4207        move[3]>='0' && move[3]<='9' &&
4208        move[2]>='a' && move[2]<='x'    ) {
4209          /* output move, normal -> Shogi */
4210         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4211         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4212         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4213         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4214         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4215     }
4216     if (appData.debugMode) {
4217         fprintf(debugFP, "   out = '%s'\n", move);
4218     }
4219 }
4220
4221 /* Parser for moves from gnuchess, ICS, or user typein box */
4222 Boolean
4223 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4224      char *move;
4225      int moveNum;
4226      ChessMove *moveType;
4227      int *fromX, *fromY, *toX, *toY;
4228      char *promoChar;
4229 {       
4230     if (appData.debugMode) {
4231         fprintf(debugFP, "move to parse: %s\n", move);
4232     }
4233     *moveType = yylexstr(moveNum, move);
4234
4235     switch (*moveType) {
4236       case WhitePromotionChancellor:
4237       case BlackPromotionChancellor:
4238       case WhitePromotionArchbishop:
4239       case BlackPromotionArchbishop:
4240       case WhitePromotionQueen:
4241       case BlackPromotionQueen:
4242       case WhitePromotionRook:
4243       case BlackPromotionRook:
4244       case WhitePromotionBishop:
4245       case BlackPromotionBishop:
4246       case WhitePromotionKnight:
4247       case BlackPromotionKnight:
4248       case WhitePromotionKing:
4249       case BlackPromotionKing:
4250       case NormalMove:
4251       case WhiteCapturesEnPassant:
4252       case BlackCapturesEnPassant:
4253       case WhiteKingSideCastle:
4254       case WhiteQueenSideCastle:
4255       case BlackKingSideCastle:
4256       case BlackQueenSideCastle:
4257       case WhiteKingSideCastleWild:
4258       case WhiteQueenSideCastleWild:
4259       case BlackKingSideCastleWild:
4260       case BlackQueenSideCastleWild:
4261       /* Code added by Tord: */
4262       case WhiteHSideCastleFR:
4263       case WhiteASideCastleFR:
4264       case BlackHSideCastleFR:
4265       case BlackASideCastleFR:
4266       /* End of code added by Tord */
4267       case IllegalMove:         /* bug or odd chess variant */
4268         *fromX = currentMoveString[0] - AAA;
4269         *fromY = currentMoveString[1] - ONE;
4270         *toX = currentMoveString[2] - AAA;
4271         *toY = currentMoveString[3] - ONE;
4272         *promoChar = currentMoveString[4];
4273         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4274             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4275     if (appData.debugMode) {
4276         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4277     }
4278             *fromX = *fromY = *toX = *toY = 0;
4279             return FALSE;
4280         }
4281         if (appData.testLegality) {
4282           return (*moveType != IllegalMove);
4283         } else {
4284           return !(fromX == fromY && toX == toY);
4285         }
4286
4287       case WhiteDrop:
4288       case BlackDrop:
4289         *fromX = *moveType == WhiteDrop ?
4290           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4291           (int) CharToPiece(ToLower(currentMoveString[0]));
4292         *fromY = DROP_RANK;
4293         *toX = currentMoveString[2] - AAA;
4294         *toY = currentMoveString[3] - ONE;
4295         *promoChar = NULLCHAR;
4296         return TRUE;
4297
4298       case AmbiguousMove:
4299       case ImpossibleMove:
4300       case (ChessMove) 0:       /* end of file */
4301       case ElapsedTime:
4302       case Comment:
4303       case PGNTag:
4304       case NAG:
4305       case WhiteWins:
4306       case BlackWins:
4307       case GameIsDrawn:
4308       default:
4309     if (appData.debugMode) {
4310         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4311     }
4312         /* bug? */
4313         *fromX = *fromY = *toX = *toY = 0;
4314         *promoChar = NULLCHAR;
4315         return FALSE;
4316     }
4317 }
4318
4319 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4320 // All positions will have equal probability, but the current method will not provide a unique
4321 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4322 #define DARK 1
4323 #define LITE 2
4324 #define ANY 3
4325
4326 int squaresLeft[4];
4327 int piecesLeft[(int)BlackPawn];
4328 int seed, nrOfShuffles;
4329
4330 void GetPositionNumber()
4331 {       // sets global variable seed
4332         int i;
4333
4334         seed = appData.defaultFrcPosition;
4335         if(seed < 0) { // randomize based on time for negative FRC position numbers
4336                 for(i=0; i<50; i++) seed += random();
4337                 seed = random() ^ random() >> 8 ^ random() << 8;
4338                 if(seed<0) seed = -seed;
4339         }
4340 }
4341
4342 int put(Board board, int pieceType, int rank, int n, int shade)
4343 // put the piece on the (n-1)-th empty squares of the given shade
4344 {
4345         int i;
4346
4347         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4348                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4349                         board[rank][i] = (ChessSquare) pieceType;
4350                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4351                         squaresLeft[ANY]--;
4352                         piecesLeft[pieceType]--; 
4353                         return i;
4354                 }
4355         }
4356         return -1;
4357 }
4358
4359
4360 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4361 // calculate where the next piece goes, (any empty square), and put it there
4362 {
4363         int i;
4364
4365         i = seed % squaresLeft[shade];
4366         nrOfShuffles *= squaresLeft[shade];
4367         seed /= squaresLeft[shade];
4368         put(board, pieceType, rank, i, shade);
4369 }
4370
4371 void AddTwoPieces(Board board, int pieceType, int rank)
4372 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4373 {
4374         int i, n=squaresLeft[ANY], j=n-1, k;
4375
4376         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4377         i = seed % k;  // pick one
4378         nrOfShuffles *= k;
4379         seed /= k;
4380         while(i >= j) i -= j--;
4381         j = n - 1 - j; i += j;
4382         put(board, pieceType, rank, j, ANY);
4383         put(board, pieceType, rank, i, ANY);
4384 }
4385
4386 void SetUpShuffle(Board board, int number)
4387 {
4388         int i, p, first=1;
4389
4390         GetPositionNumber(); nrOfShuffles = 1;
4391
4392         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4393         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4394         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4395
4396         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4397
4398         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4399             p = (int) board[0][i];
4400             if(p < (int) BlackPawn) piecesLeft[p] ++;
4401             board[0][i] = EmptySquare;
4402         }
4403
4404         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4405             // shuffles restricted to allow normal castling put KRR first
4406             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4407                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4408             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4409                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4410             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4411                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4412             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4413                 put(board, WhiteRook, 0, 0, ANY);
4414             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4415         }
4416
4417         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4418             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4419             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4420                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4421                 while(piecesLeft[p] >= 2) {
4422                     AddOnePiece(board, p, 0, LITE);
4423                     AddOnePiece(board, p, 0, DARK);
4424                 }
4425                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4426             }
4427
4428         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4429             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4430             // but we leave King and Rooks for last, to possibly obey FRC restriction
4431             if(p == (int)WhiteRook) continue;
4432             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4433             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4434         }
4435
4436         // now everything is placed, except perhaps King (Unicorn) and Rooks
4437
4438         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4439             // Last King gets castling rights
4440             while(piecesLeft[(int)WhiteUnicorn]) {
4441                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4442                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4443             }
4444
4445             while(piecesLeft[(int)WhiteKing]) {
4446                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4447                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4448             }
4449
4450
4451         } else {
4452             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4453             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4454         }
4455
4456         // Only Rooks can be left; simply place them all
4457         while(piecesLeft[(int)WhiteRook]) {
4458                 i = put(board, WhiteRook, 0, 0, ANY);
4459                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4460                         if(first) {
4461                                 first=0;
4462                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4463                         }
4464                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4465                 }
4466         }
4467         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4468             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4469         }
4470
4471         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4472 }
4473
4474 int SetCharTable( char *table, const char * map )
4475 /* [HGM] moved here from winboard.c because of its general usefulness */
4476 /*       Basically a safe strcpy that uses the last character as King */
4477 {
4478     int result = FALSE; int NrPieces;
4479
4480     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4481                     && NrPieces >= 12 && !(NrPieces&1)) {
4482         int i; /* [HGM] Accept even length from 12 to 34 */
4483
4484         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4485         for( i=0; i<NrPieces/2-1; i++ ) {
4486             table[i] = map[i];
4487             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4488         }
4489         table[(int) WhiteKing]  = map[NrPieces/2-1];
4490         table[(int) BlackKing]  = map[NrPieces-1];
4491
4492         result = TRUE;
4493     }
4494
4495     return result;
4496 }
4497
4498 void Prelude(Board board)
4499 {       // [HGM] superchess: random selection of exo-pieces
4500         int i, j, k; ChessSquare p; 
4501         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4502
4503         GetPositionNumber(); // use FRC position number
4504
4505         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4506             SetCharTable(pieceToChar, appData.pieceToCharTable);
4507             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4508                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4509         }
4510
4511         j = seed%4;                 seed /= 4; 
4512         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4513         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4514         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4515         j = seed%3 + (seed%3 >= j); seed /= 3; 
4516         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4517         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4518         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4519         j = seed%3;                 seed /= 3; 
4520         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4521         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4522         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4523         j = seed%2 + (seed%2 >= j); seed /= 2; 
4524         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4525         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4526         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4527         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4528         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4529         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4530         put(board, exoPieces[0],    0, 0, ANY);
4531         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4532 }
4533
4534 void
4535 InitPosition(redraw)
4536      int redraw;
4537 {
4538     ChessSquare (* pieces)[BOARD_SIZE];
4539     int i, j, pawnRow, overrule,
4540     oldx = gameInfo.boardWidth,
4541     oldy = gameInfo.boardHeight,
4542     oldh = gameInfo.holdingsWidth,
4543     oldv = gameInfo.variant;
4544
4545     currentMove = forwardMostMove = backwardMostMove = 0;
4546     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4547
4548     /* [AS] Initialize pv info list [HGM] and game status */
4549     {
4550         for( i=0; i<MAX_MOVES; i++ ) {
4551             pvInfoList[i].depth = 0;
4552             epStatus[i]=EP_NONE;
4553             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4554         }
4555
4556         initialRulePlies = 0; /* 50-move counter start */
4557
4558         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4559         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4560     }
4561
4562     
4563     /* [HGM] logic here is completely changed. In stead of full positions */
4564     /* the initialized data only consist of the two backranks. The switch */
4565     /* selects which one we will use, which is than copied to the Board   */
4566     /* initialPosition, which for the rest is initialized by Pawns and    */
4567     /* empty squares. This initial position is then copied to boards[0],  */
4568     /* possibly after shuffling, so that it remains available.            */
4569
4570     gameInfo.holdingsWidth = 0; /* default board sizes */
4571     gameInfo.boardWidth    = 8;
4572     gameInfo.boardHeight   = 8;
4573     gameInfo.holdingsSize  = 0;
4574     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4575     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4576     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4577
4578     switch (gameInfo.variant) {
4579     case VariantFischeRandom:
4580       shuffleOpenings = TRUE;
4581     default:
4582       pieces = FIDEArray;
4583       break;
4584     case VariantShatranj:
4585       pieces = ShatranjArray;
4586       nrCastlingRights = 0;
4587       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4588       break;
4589     case VariantTwoKings:
4590       pieces = twoKingsArray;
4591       break;
4592     case VariantCapaRandom:
4593       shuffleOpenings = TRUE;
4594     case VariantCapablanca:
4595       pieces = CapablancaArray;
4596       gameInfo.boardWidth = 10;
4597       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4598       break;
4599     case VariantGothic:
4600       pieces = GothicArray;
4601       gameInfo.boardWidth = 10;
4602       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4603       break;
4604     case VariantJanus:
4605       pieces = JanusArray;
4606       gameInfo.boardWidth = 10;
4607       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4608       nrCastlingRights = 6;
4609         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4610         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4611         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4612         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4613         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4614         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4615       break;
4616     case VariantFalcon:
4617       pieces = FalconArray;
4618       gameInfo.boardWidth = 10;
4619       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4620       break;
4621     case VariantXiangqi:
4622       pieces = XiangqiArray;
4623       gameInfo.boardWidth  = 9;
4624       gameInfo.boardHeight = 10;
4625       nrCastlingRights = 0;
4626       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4627       break;
4628     case VariantShogi:
4629       pieces = ShogiArray;
4630       gameInfo.boardWidth  = 9;
4631       gameInfo.boardHeight = 9;
4632       gameInfo.holdingsSize = 7;
4633       nrCastlingRights = 0;
4634       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4635       break;
4636     case VariantCourier:
4637       pieces = CourierArray;
4638       gameInfo.boardWidth  = 12;
4639       nrCastlingRights = 0;
4640       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4641       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4642       break;
4643     case VariantKnightmate:
4644       pieces = KnightmateArray;
4645       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4646       break;
4647     case VariantFairy:
4648       pieces = fairyArray;
4649       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4650       break;
4651     case VariantGreat:
4652       pieces = GreatArray;
4653       gameInfo.boardWidth = 10;
4654       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4655       gameInfo.holdingsSize = 8;
4656       break;
4657     case VariantSuper:
4658       pieces = FIDEArray;
4659       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4660       gameInfo.holdingsSize = 8;
4661       startedFromSetupPosition = TRUE;
4662       break;
4663     case VariantCrazyhouse:
4664     case VariantBughouse:
4665       pieces = FIDEArray;
4666       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4667       gameInfo.holdingsSize = 5;
4668       break;
4669     case VariantWildCastle:
4670       pieces = FIDEArray;
4671       /* !!?shuffle with kings guaranteed to be on d or e file */
4672       shuffleOpenings = 1;
4673       break;
4674     case VariantNoCastle:
4675       pieces = FIDEArray;
4676       nrCastlingRights = 0;
4677       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4678       /* !!?unconstrained back-rank shuffle */
4679       shuffleOpenings = 1;
4680       break;
4681     }
4682
4683     overrule = 0;
4684     if(appData.NrFiles >= 0) {
4685         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4686         gameInfo.boardWidth = appData.NrFiles;
4687     }
4688     if(appData.NrRanks >= 0) {
4689         gameInfo.boardHeight = appData.NrRanks;
4690     }
4691     if(appData.holdingsSize >= 0) {
4692         i = appData.holdingsSize;
4693         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4694         gameInfo.holdingsSize = i;
4695     }
4696     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4697     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4698         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4699
4700     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4701     if(pawnRow < 1) pawnRow = 1;
4702
4703     /* User pieceToChar list overrules defaults */
4704     if(appData.pieceToCharTable != NULL)
4705         SetCharTable(pieceToChar, appData.pieceToCharTable);
4706
4707     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4708
4709         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4710             s = (ChessSquare) 0; /* account holding counts in guard band */
4711         for( i=0; i<BOARD_HEIGHT; i++ )
4712             initialPosition[i][j] = s;
4713
4714         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4715         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4716         initialPosition[pawnRow][j] = WhitePawn;
4717         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4718         if(gameInfo.variant == VariantXiangqi) {
4719             if(j&1) {
4720                 initialPosition[pawnRow][j] = 
4721                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4722                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4723                    initialPosition[2][j] = WhiteCannon;
4724                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4725                 }
4726             }
4727         }
4728         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4729     }
4730     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4731
4732             j=BOARD_LEFT+1;
4733             initialPosition[1][j] = WhiteBishop;
4734             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4735             j=BOARD_RGHT-2;
4736             initialPosition[1][j] = WhiteRook;
4737             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4738     }
4739
4740     if( nrCastlingRights == -1) {
4741         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4742         /*       This sets default castling rights from none to normal corners   */
4743         /* Variants with other castling rights must set them themselves above    */
4744         nrCastlingRights = 6;
4745        
4746         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4747         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4748         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4749         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4750         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4751         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4752      }
4753
4754      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4755      if(gameInfo.variant == VariantGreat) { // promotion commoners
4756         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4757         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4758         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4759         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4760      }
4761 #if 0
4762     if(gameInfo.variant == VariantFischeRandom) {
4763       if( appData.defaultFrcPosition < 0 ) {
4764         ShuffleFRC( initialPosition );
4765       }
4766       else {
4767         SetupFRC( initialPosition, appData.defaultFrcPosition );
4768       }
4769       startedFromSetupPosition = TRUE;
4770     } else 
4771 #else
4772   if (appData.debugMode) {
4773     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4774   }
4775     if(shuffleOpenings) {
4776         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4777         startedFromSetupPosition = TRUE;
4778     }
4779 #endif
4780     if(startedFromPositionFile) {
4781       /* [HGM] loadPos: use PositionFile for every new game */
4782       CopyBoard(initialPosition, filePosition);
4783       for(i=0; i<nrCastlingRights; i++)
4784           castlingRights[0][i] = initialRights[i] = fileRights[i];
4785       startedFromSetupPosition = TRUE;
4786     }
4787
4788     CopyBoard(boards[0], initialPosition);
4789
4790     if(oldx != gameInfo.boardWidth ||
4791        oldy != gameInfo.boardHeight ||
4792        oldh != gameInfo.holdingsWidth
4793 #ifdef GOTHIC
4794        || oldv == VariantGothic ||        // For licensing popups
4795        gameInfo.variant == VariantGothic
4796 #endif
4797 #ifdef FALCON
4798        || oldv == VariantFalcon ||
4799        gameInfo.variant == VariantFalcon
4800 #endif
4801                                          )
4802             InitDrawingSizes(-2 ,0);
4803
4804     if (redraw)
4805       DrawPosition(TRUE, boards[currentMove]);
4806 }
4807
4808 void
4809 SendBoard(cps, moveNum)
4810      ChessProgramState *cps;
4811      int moveNum;
4812 {
4813     char message[MSG_SIZ];
4814     
4815     if (cps->useSetboard) {
4816       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4817       sprintf(message, "setboard %s\n", fen);
4818       SendToProgram(message, cps);
4819       free(fen);
4820
4821     } else {
4822       ChessSquare *bp;
4823       int i, j;
4824       /* Kludge to set black to move, avoiding the troublesome and now
4825        * deprecated "black" command.
4826        */
4827       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4828
4829       SendToProgram("edit\n", cps);
4830       SendToProgram("#\n", cps);
4831       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4832         bp = &boards[moveNum][i][BOARD_LEFT];
4833         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4834           if ((int) *bp < (int) BlackPawn) {
4835             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
4836                     AAA + j, ONE + i);
4837             if(message[0] == '+' || message[0] == '~') {
4838                 sprintf(message, "%c%c%c+\n",
4839                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4840                         AAA + j, ONE + i);
4841             }
4842             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4843                 message[1] = BOARD_RGHT   - 1 - j + '1';
4844                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4845             }
4846             SendToProgram(message, cps);
4847           }
4848         }
4849       }
4850     
4851       SendToProgram("c\n", cps);
4852       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4853         bp = &boards[moveNum][i][BOARD_LEFT];
4854         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4855           if (((int) *bp != (int) EmptySquare)
4856               && ((int) *bp >= (int) BlackPawn)) {
4857             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4858                     AAA + j, ONE + i);
4859             if(message[0] == '+' || message[0] == '~') {
4860                 sprintf(message, "%c%c%c+\n",
4861                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4862                         AAA + j, ONE + i);
4863             }
4864             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4865                 message[1] = BOARD_RGHT   - 1 - j + '1';
4866                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4867             }
4868             SendToProgram(message, cps);
4869           }
4870         }
4871       }
4872     
4873       SendToProgram(".\n", cps);
4874     }
4875     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4876 }
4877
4878 int
4879 IsPromotion(fromX, fromY, toX, toY)
4880      int fromX, fromY, toX, toY;
4881 {
4882     /* [HGM] add Shogi promotions */
4883     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4884     ChessSquare piece;
4885
4886     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4887       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4888    /* [HGM] Note to self: line above also weeds out drops */
4889     piece = boards[currentMove][fromY][fromX];
4890     if(gameInfo.variant == VariantShogi) {
4891         promotionZoneSize = 3;
4892         highestPromotingPiece = (int)WhiteKing;
4893         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4894            and if in normal chess we then allow promotion to King, why not
4895            allow promotion of other piece in Shogi?                         */
4896     }
4897     if((int)piece >= BlackPawn) {
4898         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4899              return FALSE;
4900         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4901     } else {
4902         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4903            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4904     }
4905     return ( (int)piece <= highestPromotingPiece );
4906 }
4907
4908 int
4909 InPalace(row, column)
4910      int row, column;
4911 {   /* [HGM] for Xiangqi */
4912     if( (row < 3 || row > BOARD_HEIGHT-4) &&
4913          column < (BOARD_WIDTH + 4)/2 &&
4914          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4915     return FALSE;
4916 }
4917
4918 int
4919 PieceForSquare (x, y)
4920      int x;
4921      int y;
4922 {
4923   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4924      return -1;
4925   else
4926      return boards[currentMove][y][x];
4927 }
4928
4929 int
4930 OKToStartUserMove(x, y)
4931      int x, y;
4932 {
4933     ChessSquare from_piece;
4934     int white_piece;
4935
4936     if (matchMode) return FALSE;
4937     if (gameMode == EditPosition) return TRUE;
4938
4939     if (x >= 0 && y >= 0)
4940       from_piece = boards[currentMove][y][x];
4941     else
4942       from_piece = EmptySquare;
4943
4944     if (from_piece == EmptySquare) return FALSE;
4945
4946     white_piece = (int)from_piece >= (int)WhitePawn &&
4947       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4948
4949     switch (gameMode) {
4950       case PlayFromGameFile:
4951       case AnalyzeFile:
4952       case TwoMachinesPlay:
4953       case EndOfGame:
4954         return FALSE;
4955
4956       case IcsObserving:
4957       case IcsIdle:
4958         return FALSE;
4959
4960       case MachinePlaysWhite:
4961       case IcsPlayingBlack:
4962         if (appData.zippyPlay) return FALSE;
4963         if (white_piece) {
4964             DisplayMoveError(_("You are playing Black"));
4965             return FALSE;
4966         }
4967         break;
4968
4969       case MachinePlaysBlack:
4970       case IcsPlayingWhite:
4971         if (appData.zippyPlay) return FALSE;
4972         if (!white_piece) {
4973             DisplayMoveError(_("You are playing White"));
4974             return FALSE;
4975         }
4976         break;
4977
4978       case EditGame:
4979         if (!white_piece && WhiteOnMove(currentMove)) {
4980             DisplayMoveError(_("It is White's turn"));
4981             return FALSE;
4982         }           
4983         if (white_piece && !WhiteOnMove(currentMove)) {
4984             DisplayMoveError(_("It is Black's turn"));
4985             return FALSE;
4986         }           
4987         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4988             /* Editing correspondence game history */
4989             /* Could disallow this or prompt for confirmation */
4990             cmailOldMove = -1;
4991         }
4992         if (currentMove < forwardMostMove) {
4993             /* Discarding moves */
4994             /* Could prompt for confirmation here,
4995                but I don't think that's such a good idea */
4996             forwardMostMove = currentMove;
4997         }
4998         break;
4999
5000       case BeginningOfGame:
5001         if (appData.icsActive) return FALSE;
5002         if (!appData.noChessProgram) {
5003             if (!white_piece) {
5004                 DisplayMoveError(_("You are playing White"));
5005                 return FALSE;
5006             }
5007         }
5008         break;
5009         
5010       case Training:
5011         if (!white_piece && WhiteOnMove(currentMove)) {
5012             DisplayMoveError(_("It is White's turn"));
5013             return FALSE;
5014         }           
5015         if (white_piece && !WhiteOnMove(currentMove)) {
5016             DisplayMoveError(_("It is Black's turn"));
5017             return FALSE;
5018         }           
5019         break;
5020
5021       default:
5022       case IcsExamining:
5023         break;
5024     }
5025     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5026         && gameMode != AnalyzeFile && gameMode != Training) {
5027         DisplayMoveError(_("Displayed position is not current"));
5028         return FALSE;
5029     }
5030     return TRUE;
5031 }
5032
5033 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5034 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5035 int lastLoadGameUseList = FALSE;
5036 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5037 ChessMove lastLoadGameStart = (ChessMove) 0;
5038
5039
5040 ChessMove
5041 UserMoveTest(fromX, fromY, toX, toY, promoChar)
5042      int fromX, fromY, toX, toY;
5043      int promoChar;
5044 {
5045     ChessMove moveType;
5046     ChessSquare pdown, pup;
5047
5048     if (fromX < 0 || fromY < 0) return ImpossibleMove;
5049     if ((fromX == toX) && (fromY == toY)) {
5050         return ImpossibleMove;
5051     }
5052
5053     /* [HGM] suppress all moves into holdings area and guard band */
5054     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5055             return ImpossibleMove;
5056
5057     /* [HGM] <sameColor> moved to here from winboard.c */
5058     /* note: this code seems to exist for filtering out some obviously illegal premoves */
5059     pdown = boards[currentMove][fromY][fromX];
5060     pup = boards[currentMove][toY][toX];
5061     if (    gameMode != EditPosition &&
5062             (WhitePawn <= pdown && pdown < BlackPawn &&
5063              WhitePawn <= pup && pup < BlackPawn  ||
5064              BlackPawn <= pdown && pdown < EmptySquare &&
5065              BlackPawn <= pup && pup < EmptySquare 
5066             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5067                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5068                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) 
5069         )           )
5070          return ImpossibleMove;
5071
5072     /* Check if the user is playing in turn.  This is complicated because we
5073        let the user "pick up" a piece before it is his turn.  So the piece he
5074        tried to pick up may have been captured by the time he puts it down!
5075        Therefore we use the color the user is supposed to be playing in this
5076        test, not the color of the piece that is currently on the starting
5077        square---except in EditGame mode, where the user is playing both
5078        sides; fortunately there the capture race can't happen.  (It can
5079        now happen in IcsExamining mode, but that's just too bad.  The user
5080        will get a somewhat confusing message in that case.)
5081        */
5082
5083     switch (gameMode) {
5084       case PlayFromGameFile:
5085       case AnalyzeFile:
5086       case TwoMachinesPlay:
5087       case EndOfGame:
5088       case IcsObserving:
5089       case IcsIdle:
5090         /* We switched into a game mode where moves are not accepted,
5091            perhaps while the mouse button was down. */
5092         return ImpossibleMove;
5093
5094       case MachinePlaysWhite:
5095         /* User is moving for Black */
5096         if (WhiteOnMove(currentMove)) {
5097             DisplayMoveError(_("It is White's turn"));
5098             return ImpossibleMove;
5099         }
5100         break;
5101
5102       case MachinePlaysBlack:
5103         /* User is moving for White */
5104         if (!WhiteOnMove(currentMove)) {
5105             DisplayMoveError(_("It is Black's turn"));
5106             return ImpossibleMove;
5107         }
5108         break;
5109
5110       case EditGame:
5111       case IcsExamining:
5112       case BeginningOfGame:
5113       case AnalyzeMode:
5114       case Training:
5115         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5116             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5117             /* User is moving for Black */
5118             if (WhiteOnMove(currentMove)) {
5119                 DisplayMoveError(_("It is White's turn"));
5120                 return ImpossibleMove;
5121             }
5122         } else {
5123             /* User is moving for White */
5124             if (!WhiteOnMove(currentMove)) {
5125                 DisplayMoveError(_("It is Black's turn"));
5126                 return ImpossibleMove;
5127             }
5128         }
5129         break;
5130
5131       case IcsPlayingBlack:
5132         /* User is moving for Black */
5133         if (WhiteOnMove(currentMove)) {
5134             if (!appData.premove) {
5135                 DisplayMoveError(_("It is White's turn"));
5136             } else if (toX >= 0 && toY >= 0) {
5137                 premoveToX = toX;
5138                 premoveToY = toY;
5139                 premoveFromX = fromX;
5140                 premoveFromY = fromY;
5141                 premovePromoChar = promoChar;
5142                 gotPremove = 1;
5143                 if (appData.debugMode) 
5144                     fprintf(debugFP, "Got premove: fromX %d,"
5145                             "fromY %d, toX %d, toY %d\n",
5146                             fromX, fromY, toX, toY);
5147             }
5148             return ImpossibleMove;
5149         }
5150         break;
5151
5152       case IcsPlayingWhite:
5153         /* User is moving for White */
5154         if (!WhiteOnMove(currentMove)) {
5155             if (!appData.premove) {
5156                 DisplayMoveError(_("It is Black's turn"));
5157             } else if (toX >= 0 && toY >= 0) {
5158                 premoveToX = toX;
5159                 premoveToY = toY;
5160                 premoveFromX = fromX;
5161                 premoveFromY = fromY;
5162                 premovePromoChar = promoChar;
5163                 gotPremove = 1;
5164                 if (appData.debugMode) 
5165                     fprintf(debugFP, "Got premove: fromX %d,"
5166                             "fromY %d, toX %d, toY %d\n",
5167                             fromX, fromY, toX, toY);
5168             }
5169             return ImpossibleMove;
5170         }
5171         break;
5172
5173       default:
5174         break;
5175
5176       case EditPosition:
5177         /* EditPosition, empty square, or different color piece;
5178            click-click move is possible */
5179         if (toX == -2 || toY == -2) {
5180             boards[0][fromY][fromX] = EmptySquare;
5181             return AmbiguousMove;
5182         } else if (toX >= 0 && toY >= 0) {
5183             boards[0][toY][toX] = boards[0][fromY][fromX];
5184             boards[0][fromY][fromX] = EmptySquare;
5185             return AmbiguousMove;
5186         }
5187         return ImpossibleMove;
5188     }
5189
5190     /* [HGM] If move started in holdings, it means a drop */
5191     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5192          if( pup != EmptySquare ) return ImpossibleMove;
5193          if(appData.testLegality) {
5194              /* it would be more logical if LegalityTest() also figured out
5195               * which drops are legal. For now we forbid pawns on back rank.
5196               * Shogi is on its own here...
5197               */
5198              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5199                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5200                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5201          }
5202          return WhiteDrop; /* Not needed to specify white or black yet */
5203     }
5204
5205     userOfferedDraw = FALSE;
5206         
5207     /* [HGM] always test for legality, to get promotion info */
5208     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5209                           epStatus[currentMove], castlingRights[currentMove],
5210                                          fromY, fromX, toY, toX, promoChar);
5211
5212     /* [HGM] but possibly ignore an IllegalMove result */
5213     if (appData.testLegality) {
5214         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5215             DisplayMoveError(_("Illegal move"));
5216             return ImpossibleMove;
5217         }
5218     }
5219 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5220     return moveType;
5221     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5222        function is made into one that returns an OK move type if FinishMove
5223        should be called. This to give the calling driver routine the
5224        opportunity to finish the userMove input with a promotion popup,
5225        without bothering the user with this for invalid or illegal moves */
5226
5227 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5228 }
5229
5230 /* Common tail of UserMoveEvent and DropMenuEvent */
5231 int
5232 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5233      ChessMove moveType;
5234      int fromX, fromY, toX, toY;
5235      /*char*/int promoChar;
5236 {
5237     char *bookHit = 0;
5238 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5239     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5240         // [HGM] superchess: suppress promotions to non-available piece
5241         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5242         if(WhiteOnMove(currentMove)) {
5243             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5244         } else {
5245             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5246         }
5247     }
5248
5249     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5250        move type in caller when we know the move is a legal promotion */
5251     if(moveType == NormalMove && promoChar)
5252         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5253 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5254     /* [HGM] convert drag-and-drop piece drops to standard form */
5255     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5256          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5257            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5258                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5259 //         fromX = boards[currentMove][fromY][fromX];
5260            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5261            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5262            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5263            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5264          fromY = DROP_RANK;
5265     }
5266
5267     /* [HGM] <popupFix> The following if has been moved here from
5268        UserMoveEvent(). Because it seemed to belon here (why not allow
5269        piece drops in training games?), and because it can only be
5270        performed after it is known to what we promote. */
5271     if (gameMode == Training) {
5272       /* compare the move played on the board to the next move in the
5273        * game. If they match, display the move and the opponent's response. 
5274        * If they don't match, display an error message.
5275        */
5276       int saveAnimate;
5277       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5278       CopyBoard(testBoard, boards[currentMove]);
5279       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5280
5281       if (CompareBoards(testBoard, boards[currentMove+1])) {
5282         ForwardInner(currentMove+1);
5283
5284         /* Autoplay the opponent's response.
5285          * if appData.animate was TRUE when Training mode was entered,
5286          * the response will be animated.
5287          */
5288         saveAnimate = appData.animate;
5289         appData.animate = animateTraining;
5290         ForwardInner(currentMove+1);
5291         appData.animate = saveAnimate;
5292
5293         /* check for the end of the game */
5294         if (currentMove >= forwardMostMove) {
5295           gameMode = PlayFromGameFile;
5296           ModeHighlight();
5297           SetTrainingModeOff();
5298           DisplayInformation(_("End of game"));
5299         }
5300       } else {
5301         DisplayError(_("Incorrect move"), 0);
5302       }
5303       return 1;
5304     }
5305
5306   /* Ok, now we know that the move is good, so we can kill
5307      the previous line in Analysis Mode */
5308   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5309     forwardMostMove = currentMove;
5310   }
5311
5312   /* If we need the chess program but it's dead, restart it */
5313   ResurrectChessProgram();
5314
5315   /* A user move restarts a paused game*/
5316   if (pausing)
5317     PauseEvent();
5318
5319   thinkOutput[0] = NULLCHAR;
5320
5321   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5322
5323   if (gameMode == BeginningOfGame) {
5324     if (appData.noChessProgram) {
5325       gameMode = EditGame;
5326       SetGameInfo();
5327     } else {
5328       char buf[MSG_SIZ];
5329       gameMode = MachinePlaysBlack;
5330       StartClocks();
5331       SetGameInfo();
5332       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5333       DisplayTitle(buf);
5334       if (first.sendName) {
5335         sprintf(buf, "name %s\n", gameInfo.white);
5336         SendToProgram(buf, &first);
5337       }
5338       StartClocks();
5339     }
5340     ModeHighlight();
5341   }
5342 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5343   /* Relay move to ICS or chess engine */
5344   if (appData.icsActive) {
5345     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5346         gameMode == IcsExamining) {
5347       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5348       ics_user_moved = 1;
5349     }
5350   } else {
5351     if (first.sendTime && (gameMode == BeginningOfGame ||
5352                            gameMode == MachinePlaysWhite ||
5353                            gameMode == MachinePlaysBlack)) {
5354       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5355     }
5356     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5357          // [HGM] book: if program might be playing, let it use book
5358         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5359         first.maybeThinking = TRUE;
5360     } else SendMoveToProgram(forwardMostMove-1, &first);
5361     if (currentMove == cmailOldMove + 1) {
5362       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5363     }
5364   }
5365
5366   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5367
5368   switch (gameMode) {
5369   case EditGame:
5370     switch (MateTest(boards[currentMove], PosFlags(currentMove),
5371                      EP_UNKNOWN, castlingRights[currentMove]) ) {
5372     case MT_NONE:
5373     case MT_CHECK:
5374       break;
5375     case MT_CHECKMATE:
5376     case MT_STAINMATE:
5377       if (WhiteOnMove(currentMove)) {
5378         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5379       } else {
5380         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5381       }
5382       break;
5383     case MT_STALEMATE:
5384       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5385       break;
5386     }
5387     break;
5388     
5389   case MachinePlaysBlack:
5390   case MachinePlaysWhite:
5391     /* disable certain menu options while machine is thinking */
5392     SetMachineThinkingEnables();
5393     break;
5394
5395   default:
5396     break;
5397   }
5398
5399   if(bookHit) { // [HGM] book: simulate book reply
5400         static char bookMove[MSG_SIZ]; // a bit generous?
5401
5402         programStats.nodes = programStats.depth = programStats.time = 
5403         programStats.score = programStats.got_only_move = 0;
5404         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5405
5406         strcpy(bookMove, "move ");
5407         strcat(bookMove, bookHit);
5408         HandleMachineMove(bookMove, &first);
5409   }
5410   return 1;
5411 }
5412
5413 void
5414 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5415      int fromX, fromY, toX, toY;
5416      int promoChar;
5417 {
5418     /* [HGM] This routine was added to allow calling of its two logical
5419        parts from other modules in the old way. Before, UserMoveEvent()
5420        automatically called FinishMove() if the move was OK, and returned
5421        otherwise. I separated the two, in order to make it possible to
5422        slip a promotion popup in between. But that it always needs two
5423        calls, to the first part, (now called UserMoveTest() ), and to
5424        FinishMove if the first part succeeded. Calls that do not need
5425        to do anything in between, can call this routine the old way. 
5426     */
5427     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
5428 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5429     if(moveType != ImpossibleMove)
5430         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5431 }
5432
5433 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5434 {
5435 //    char * hint = lastHint;
5436     FrontEndProgramStats stats;
5437
5438     stats.which = cps == &first ? 0 : 1;
5439     stats.depth = cpstats->depth;
5440     stats.nodes = cpstats->nodes;
5441     stats.score = cpstats->score;
5442     stats.time = cpstats->time;
5443     stats.pv = cpstats->movelist;
5444     stats.hint = lastHint;
5445     stats.an_move_index = 0;
5446     stats.an_move_count = 0;
5447
5448     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5449         stats.hint = cpstats->move_name;
5450         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5451         stats.an_move_count = cpstats->nr_moves;
5452     }
5453
5454     SetProgramStats( &stats );
5455 }
5456
5457 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5458 {   // [HGM] book: this routine intercepts moves to simulate book replies
5459     char *bookHit = NULL;
5460
5461     //first determine if the incoming move brings opponent into his book
5462     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5463         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5464     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5465     if(bookHit != NULL && !cps->bookSuspend) {
5466         // make sure opponent is not going to reply after receiving move to book position
5467         SendToProgram("force\n", cps);
5468         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5469     }
5470     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5471     // now arrange restart after book miss
5472     if(bookHit) {
5473         // after a book hit we never send 'go', and the code after the call to this routine
5474         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5475         char buf[MSG_SIZ];
5476         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5477         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5478         SendToProgram(buf, cps);
5479         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5480     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5481         SendToProgram("go\n", cps);
5482         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5483     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5484         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5485             SendToProgram("go\n", cps); 
5486         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5487     }
5488     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5489 }
5490
5491 char *savedMessage;
5492 ChessProgramState *savedState;
5493 void DeferredBookMove(void)
5494 {
5495         if(savedState->lastPing != savedState->lastPong)
5496                     ScheduleDelayedEvent(DeferredBookMove, 10);
5497         else
5498         HandleMachineMove(savedMessage, savedState);
5499 }
5500
5501 void
5502 HandleMachineMove(message, cps)
5503      char *message;
5504      ChessProgramState *cps;
5505 {
5506     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5507     char realname[MSG_SIZ];
5508     int fromX, fromY, toX, toY;
5509     ChessMove moveType;
5510     char promoChar;
5511     char *p;
5512     int machineWhite;
5513     char *bookHit;
5514
5515 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5516     /*
5517      * Kludge to ignore BEL characters
5518      */
5519     while (*message == '\007') message++;
5520
5521     /*
5522      * [HGM] engine debug message: ignore lines starting with '#' character
5523      */
5524     if(cps->debug && *message == '#') return;
5525
5526     /*
5527      * Look for book output
5528      */
5529     if (cps == &first && bookRequested) {
5530         if (message[0] == '\t' || message[0] == ' ') {
5531             /* Part of the book output is here; append it */
5532             strcat(bookOutput, message);
5533             strcat(bookOutput, "  \n");
5534             return;
5535         } else if (bookOutput[0] != NULLCHAR) {
5536             /* All of book output has arrived; display it */
5537             char *p = bookOutput;
5538             while (*p != NULLCHAR) {
5539                 if (*p == '\t') *p = ' ';
5540                 p++;
5541             }
5542             DisplayInformation(bookOutput);
5543             bookRequested = FALSE;
5544             /* Fall through to parse the current output */
5545         }
5546     }
5547
5548     /*
5549      * Look for machine move.
5550      */
5551     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5552         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
5553     {
5554         /* This method is only useful on engines that support ping */
5555         if (cps->lastPing != cps->lastPong) {
5556           if (gameMode == BeginningOfGame) {
5557             /* Extra move from before last new; ignore */
5558             if (appData.debugMode) {
5559                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5560             }
5561           } else {
5562             if (appData.debugMode) {
5563                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5564                         cps->which, gameMode);
5565             }
5566
5567             SendToProgram("undo\n", cps);
5568           }
5569           return;
5570         }
5571
5572         switch (gameMode) {
5573           case BeginningOfGame:
5574             /* Extra move from before last reset; ignore */
5575             if (appData.debugMode) {
5576                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5577             }
5578             return;
5579
5580           case EndOfGame:
5581           case IcsIdle:
5582           default:
5583             /* Extra move after we tried to stop.  The mode test is
5584                not a reliable way of detecting this problem, but it's
5585                the best we can do on engines that don't support ping.
5586             */
5587             if (appData.debugMode) {
5588                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5589                         cps->which, gameMode);
5590             }
5591             SendToProgram("undo\n", cps);
5592             return;
5593
5594           case MachinePlaysWhite:
5595           case IcsPlayingWhite:
5596             machineWhite = TRUE;
5597             break;
5598
5599           case MachinePlaysBlack:
5600           case IcsPlayingBlack:
5601             machineWhite = FALSE;
5602             break;
5603
5604           case TwoMachinesPlay:
5605             machineWhite = (cps->twoMachinesColor[0] == 'w');
5606             break;
5607         }
5608         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5609             if (appData.debugMode) {
5610                 fprintf(debugFP,
5611                         "Ignoring move out of turn by %s, gameMode %d"
5612                         ", forwardMost %d\n",
5613                         cps->which, gameMode, forwardMostMove);
5614             }
5615             return;
5616         }
5617
5618     if (appData.debugMode) { int f = forwardMostMove;
5619         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5620                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5621     }
5622         if(cps->alphaRank) AlphaRank(machineMove, 4);
5623         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5624                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5625             /* Machine move could not be parsed; ignore it. */
5626             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5627                     machineMove, cps->which);
5628             DisplayError(buf1, 0);
5629             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5630                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5631             if (gameMode == TwoMachinesPlay) {
5632               GameEnds(machineWhite ? BlackWins : WhiteWins,
5633                        buf1, GE_XBOARD);
5634             }
5635             return;
5636         }
5637
5638         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5639         /* So we have to redo legality test with true e.p. status here,  */
5640         /* to make sure an illegal e.p. capture does not slip through,   */
5641         /* to cause a forfeit on a justified illegal-move complaint      */
5642         /* of the opponent.                                              */
5643         if( gameMode==TwoMachinesPlay && appData.testLegality
5644             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5645                                                               ) {
5646            ChessMove moveType;
5647            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5648                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5649                              fromY, fromX, toY, toX, promoChar);
5650             if (appData.debugMode) {
5651                 int i;
5652                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5653                     castlingRights[forwardMostMove][i], castlingRank[i]);
5654                 fprintf(debugFP, "castling rights\n");
5655             }
5656             if(moveType == IllegalMove) {
5657                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5658                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5659                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5660                            buf1, GE_XBOARD);
5661                 return;
5662            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5663            /* [HGM] Kludge to handle engines that send FRC-style castling
5664               when they shouldn't (like TSCP-Gothic) */
5665            switch(moveType) {
5666              case WhiteASideCastleFR:
5667              case BlackASideCastleFR:
5668                toX+=2;
5669                currentMoveString[2]++;
5670                break;
5671              case WhiteHSideCastleFR:
5672              case BlackHSideCastleFR:
5673                toX--;
5674                currentMoveString[2]--;
5675                break;
5676              default: ; // nothing to do, but suppresses warning of pedantic compilers
5677            }
5678         }
5679         hintRequested = FALSE;
5680         lastHint[0] = NULLCHAR;
5681         bookRequested = FALSE;
5682         /* Program may be pondering now */
5683         cps->maybeThinking = TRUE;
5684         if (cps->sendTime == 2) cps->sendTime = 1;
5685         if (cps->offeredDraw) cps->offeredDraw--;
5686
5687 #if ZIPPY
5688         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5689             first.initDone) {
5690           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5691           ics_user_moved = 1;
5692           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5693                 char buf[3*MSG_SIZ];
5694
5695                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5696                         programStats.score / 100.,
5697                         programStats.depth,
5698                         programStats.time / 100.,
5699                         (unsigned int)programStats.nodes,
5700                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5701                         programStats.movelist);
5702                 SendToICS(buf);
5703 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5704           }
5705         }
5706 #endif
5707         /* currentMoveString is set as a side-effect of ParseOneMove */
5708         strcpy(machineMove, currentMoveString);
5709         strcat(machineMove, "\n");
5710         strcpy(moveList[forwardMostMove], machineMove);
5711
5712         /* [AS] Save move info and clear stats for next move */
5713         pvInfoList[ forwardMostMove ].score = programStats.score;
5714         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5715         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5716         ClearProgramStats();
5717         thinkOutput[0] = NULLCHAR;
5718         hiddenThinkOutputState = 0;
5719
5720         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5721
5722         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5723         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5724             int count = 0;
5725
5726             while( count < adjudicateLossPlies ) {
5727                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5728
5729                 if( count & 1 ) {
5730                     score = -score; /* Flip score for winning side */
5731                 }
5732
5733                 if( score > adjudicateLossThreshold ) {
5734                     break;
5735                 }
5736
5737                 count++;
5738             }
5739
5740             if( count >= adjudicateLossPlies ) {
5741                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5742
5743                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5744                     "Xboard adjudication", 
5745                     GE_XBOARD );
5746
5747                 return;
5748             }
5749         }
5750
5751         if( gameMode == TwoMachinesPlay ) {
5752           // [HGM] some adjudications useful with buggy engines
5753             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5754           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5755
5756
5757             if( appData.testLegality )
5758             {   /* [HGM] Some more adjudications for obstinate engines */
5759                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5760                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5761                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5762                 static int moveCount = 6;
5763                 ChessMove result;
5764                 char *reason = NULL;
5765
5766                 /* Count what is on board. */
5767                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5768                 {   ChessSquare p = boards[forwardMostMove][i][j];
5769                     int m=i;
5770
5771                     switch((int) p)
5772                     {   /* count B,N,R and other of each side */
5773                         case WhiteKing:
5774                         case BlackKing:
5775                              NrK++; break; // [HGM] atomic: count Kings
5776                         case WhiteKnight:
5777                              NrWN++; break;
5778                         case WhiteBishop:
5779                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5780                              bishopsColor |= 1 << ((i^j)&1);
5781                              NrWB++; break;
5782                         case BlackKnight:
5783                              NrBN++; break;
5784                         case BlackBishop:
5785                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5786                              bishopsColor |= 1 << ((i^j)&1);
5787                              NrBB++; break;
5788                         case WhiteRook:
5789                              NrWR++; break;
5790                         case BlackRook:
5791                              NrBR++; break;
5792                         case WhiteQueen:
5793                              NrWQ++; break;
5794                         case BlackQueen:
5795                              NrBQ++; break;
5796                         case EmptySquare: 
5797                              break;
5798                         case BlackPawn:
5799                              m = 7-i;
5800                         case WhitePawn:
5801                              PawnAdvance += m; NrPawns++;
5802                     }
5803                     NrPieces += (p != EmptySquare);
5804                     NrW += ((int)p < (int)BlackPawn);
5805                     if(gameInfo.variant == VariantXiangqi && 
5806                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5807                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5808                         NrW -= ((int)p < (int)BlackPawn);
5809                     }
5810                 }
5811
5812                 /* Some material-based adjudications that have to be made before stalemate test */
5813                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5814                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5815                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5816                      if(appData.checkMates) {
5817                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5818                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5819                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
5820                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
5821                          return;
5822                      }
5823                 }
5824
5825                 /* Bare King in Shatranj (loses) or Losers (wins) */
5826                 if( NrW == 1 || NrPieces - NrW == 1) {
5827                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5828                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
5829                      if(appData.checkMates) {
5830                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5831                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5832                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
5833                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5834                          return;
5835                      }
5836                   } else
5837                   if( gameInfo.variant == VariantShatranj && --bare < 0)
5838                   {    /* bare King */
5839                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5840                         if(appData.checkMates) {
5841                             /* but only adjudicate if adjudication enabled */
5842                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5843                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5844                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
5845                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5846                             return;
5847                         }
5848                   }
5849                 } else bare = 1;
5850
5851
5852             // don't wait for engine to announce game end if we can judge ourselves
5853             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5854                                        castlingRights[forwardMostMove]) ) {
5855               case MT_CHECK:
5856                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5857                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
5858                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5859                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5860                             checkCnt++;
5861                         if(checkCnt >= 2) {
5862                             reason = "Xboard adjudication: 3rd check";
5863                             epStatus[forwardMostMove] = EP_CHECKMATE;
5864                             break;
5865                         }
5866                     }
5867                 }
5868               case MT_NONE:
5869               default:
5870                 break;
5871               case MT_STALEMATE:
5872               case MT_STAINMATE:
5873                 reason = "Xboard adjudication: Stalemate";
5874                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5875                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
5876                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5877                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
5878                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5879                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5880                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5881                                                                         EP_CHECKMATE : EP_WINS);
5882                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5883                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5884                 }
5885                 break;
5886               case MT_CHECKMATE:
5887                 reason = "Xboard adjudication: Checkmate";
5888                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5889                 break;
5890             }
5891
5892                 switch(i = epStatus[forwardMostMove]) {
5893                     case EP_STALEMATE:
5894                         result = GameIsDrawn; break;
5895                     case EP_CHECKMATE:
5896                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5897                     case EP_WINS:
5898                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5899                     default:
5900                         result = (ChessMove) 0;
5901                 }
5902                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5903                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5904                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5905                     GameEnds( result, reason, GE_XBOARD );
5906                     return;
5907                 }
5908
5909                 /* Next absolutely insufficient mating material. */
5910                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
5911                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5912                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5913                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5914                 {    /* KBK, KNK, KK of KBKB with like Bishops */
5915
5916                      /* always flag draws, for judging claims */
5917                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
5918
5919                      if(appData.materialDraws) {
5920                          /* but only adjudicate them if adjudication enabled */
5921                          SendToProgram("force\n", cps->other); // suppress reply
5922                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5923                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5924                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5925                          return;
5926                      }
5927                 }
5928
5929                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5930                 if(NrPieces == 4 && 
5931                    (   NrWR == 1 && NrBR == 1 /* KRKR */
5932                    || NrWQ==1 && NrBQ==1     /* KQKQ */
5933                    || NrWN==2 || NrBN==2     /* KNNK */
5934                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5935                   ) ) {
5936                      if(--moveCount < 0 && appData.trivialDraws)
5937                      {    /* if the first 3 moves do not show a tactical win, declare draw */
5938                           SendToProgram("force\n", cps->other); // suppress reply
5939                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5940                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5941                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5942                           return;
5943                      }
5944                 } else moveCount = 6;
5945             }
5946           }
5947 #if 1
5948     if (appData.debugMode) { int i;
5949       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5950               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5951               appData.drawRepeats);
5952       for( i=forwardMostMove; i>=backwardMostMove; i-- )
5953            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5954
5955     }
5956 #endif
5957                 /* Check for rep-draws */
5958                 count = 0;
5959                 for(k = forwardMostMove-2;
5960                     k>=backwardMostMove && k>=forwardMostMove-100 &&
5961                         epStatus[k] < EP_UNKNOWN &&
5962                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5963                     k-=2)
5964                 {   int rights=0;
5965 #if 0
5966     if (appData.debugMode) {
5967       fprintf(debugFP, " loop\n");
5968     }
5969 #endif
5970                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
5971 #if 0
5972     if (appData.debugMode) {
5973       fprintf(debugFP, "match\n");
5974     }
5975 #endif
5976                         /* compare castling rights */
5977                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5978                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5979                                 rights++; /* King lost rights, while rook still had them */
5980                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5981                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5982                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5983                                    rights++; /* but at least one rook lost them */
5984                         }
5985                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5986                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5987                                 rights++; 
5988                         if( castlingRights[forwardMostMove][5] >= 0 ) {
5989                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5990                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5991                                    rights++;
5992                         }
5993 #if 0
5994     if (appData.debugMode) {
5995       for(i=0; i<nrCastlingRights; i++)
5996       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
5997     }
5998
5999     if (appData.debugMode) {
6000       fprintf(debugFP, " %d %d\n", rights, k);
6001     }
6002 #endif
6003                         if( rights == 0 && ++count > appData.drawRepeats-2
6004                             && appData.drawRepeats > 1) {
6005                              /* adjudicate after user-specified nr of repeats */
6006                              SendToProgram("force\n", cps->other); // suppress reply
6007                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6008                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6009                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6010                                 // [HGM] xiangqi: check for forbidden perpetuals
6011                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6012                                 for(m=forwardMostMove; m>k; m-=2) {
6013                                     if(MateTest(boards[m], PosFlags(m), 
6014                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
6015                                         ourPerpetual = 0; // the current mover did not always check
6016                                     if(MateTest(boards[m-1], PosFlags(m-1), 
6017                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
6018                                         hisPerpetual = 0; // the opponent did not always check
6019                                 }
6020                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6021                                                                         ourPerpetual, hisPerpetual);
6022                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6023                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6024                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6025                                     return;
6026                                 }
6027                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6028                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6029                                 // Now check for perpetual chases
6030                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6031                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6032                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6033                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6034                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6035                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6036                                         return;
6037                                     }
6038                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6039                                         break; // Abort repetition-checking loop.
6040                                 }
6041                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6042                              }
6043                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6044                              return;
6045                         }
6046                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6047                              epStatus[forwardMostMove] = EP_REP_DRAW;
6048                     }
6049                 }
6050
6051                 /* Now we test for 50-move draws. Determine ply count */
6052                 count = forwardMostMove;
6053                 /* look for last irreversble move */
6054                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6055                     count--;
6056                 /* if we hit starting position, add initial plies */
6057                 if( count == backwardMostMove )
6058                     count -= initialRulePlies;
6059                 count = forwardMostMove - count; 
6060                 if( count >= 100)
6061                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6062                          /* this is used to judge if draw claims are legal */
6063                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6064                          SendToProgram("force\n", cps->other); // suppress reply
6065                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6066                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6067                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6068                          return;
6069                 }
6070
6071                 /* if draw offer is pending, treat it as a draw claim
6072                  * when draw condition present, to allow engines a way to
6073                  * claim draws before making their move to avoid a race
6074                  * condition occurring after their move
6075                  */
6076                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6077                          char *p = NULL;
6078                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6079                              p = "Draw claim: 50-move rule";
6080                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6081                              p = "Draw claim: 3-fold repetition";
6082                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6083                              p = "Draw claim: insufficient mating material";
6084                          if( p != NULL ) {
6085                              SendToProgram("force\n", cps->other); // suppress reply
6086                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6087                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6088                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6089                              return;
6090                          }
6091                 }
6092
6093
6094                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6095                     SendToProgram("force\n", cps->other); // suppress reply
6096                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6097                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6098
6099                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6100
6101                     return;
6102                 }
6103         }
6104
6105         bookHit = NULL;
6106         if (gameMode == TwoMachinesPlay) {
6107             /* [HGM] relaying draw offers moved to after reception of move */
6108             /* and interpreting offer as claim if it brings draw condition */
6109             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6110                 SendToProgram("draw\n", cps->other);
6111             }
6112             if (cps->other->sendTime) {
6113                 SendTimeRemaining(cps->other,
6114                                   cps->other->twoMachinesColor[0] == 'w');
6115             }
6116             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6117             if (firstMove && !bookHit) {
6118                 firstMove = FALSE;
6119                 if (cps->other->useColors) {
6120                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6121                 }
6122                 SendToProgram("go\n", cps->other);
6123             }
6124             cps->other->maybeThinking = TRUE;
6125         }
6126
6127         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6128         
6129         if (!pausing && appData.ringBellAfterMoves) {
6130             RingBell();
6131         }
6132
6133         /* 
6134          * Reenable menu items that were disabled while
6135          * machine was thinking
6136          */
6137         if (gameMode != TwoMachinesPlay)
6138             SetUserThinkingEnables();
6139
6140         // [HGM] book: after book hit opponent has received move and is now in force mode
6141         // force the book reply into it, and then fake that it outputted this move by jumping
6142         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6143         if(bookHit) {
6144                 static char bookMove[MSG_SIZ]; // a bit generous?
6145
6146                 strcpy(bookMove, "move ");
6147                 strcat(bookMove, bookHit);
6148                 message = bookMove;
6149                 cps = cps->other;
6150                 programStats.nodes = programStats.depth = programStats.time = 
6151                 programStats.score = programStats.got_only_move = 0;
6152                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6153
6154                 if(cps->lastPing != cps->lastPong) {
6155                     savedMessage = message; // args for deferred call
6156                     savedState = cps;
6157                     ScheduleDelayedEvent(DeferredBookMove, 10);
6158                     return;
6159                 }
6160                 goto FakeBookMove;
6161         }
6162
6163         return;
6164     }
6165
6166     /* Set special modes for chess engines.  Later something general
6167      *  could be added here; for now there is just one kludge feature,
6168      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6169      *  when "xboard" is given as an interactive command.
6170      */
6171     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6172         cps->useSigint = FALSE;
6173         cps->useSigterm = FALSE;
6174     }
6175     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6176       ParseFeatures(message+8, cps);
6177       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6178     }
6179
6180     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6181      * want this, I was asked to put it in, and obliged.
6182      */
6183     if (!strncmp(message, "setboard ", 9)) {
6184         Board initial_position; int i;
6185
6186         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6187
6188         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6189             DisplayError(_("Bad FEN received from engine"), 0);
6190             return ;
6191         } else {
6192            Reset(FALSE, FALSE);
6193            CopyBoard(boards[0], initial_position);
6194            initialRulePlies = FENrulePlies;
6195            epStatus[0] = FENepStatus;
6196            for( i=0; i<nrCastlingRights; i++ )
6197                 castlingRights[0][i] = FENcastlingRights[i];
6198            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6199            else gameMode = MachinePlaysBlack;                 
6200            DrawPosition(FALSE, boards[currentMove]);
6201         }
6202         return;
6203     }
6204
6205     /*
6206      * Look for communication commands
6207      */
6208     if (!strncmp(message, "telluser ", 9)) {
6209         DisplayNote(message + 9);
6210         return;
6211     }
6212     if (!strncmp(message, "tellusererror ", 14)) {
6213         DisplayError(message + 14, 0);
6214         return;
6215     }
6216     if (!strncmp(message, "tellopponent ", 13)) {
6217       if (appData.icsActive) {
6218         if (loggedOn) {
6219           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6220           SendToICS(buf1);
6221         }
6222       } else {
6223         DisplayNote(message + 13);
6224       }
6225       return;
6226     }
6227     if (!strncmp(message, "tellothers ", 11)) {
6228       if (appData.icsActive) {
6229         if (loggedOn) {
6230           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6231           SendToICS(buf1);
6232         }
6233       }
6234       return;
6235     }
6236     if (!strncmp(message, "tellall ", 8)) {
6237       if (appData.icsActive) {
6238         if (loggedOn) {
6239           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6240           SendToICS(buf1);
6241         }
6242       } else {
6243         DisplayNote(message + 8);
6244       }
6245       return;
6246     }
6247     if (strncmp(message, "warning", 7) == 0) {
6248         /* Undocumented feature, use tellusererror in new code */
6249         DisplayError(message, 0);
6250         return;
6251     }
6252     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6253         strcpy(realname, cps->tidy);
6254         strcat(realname, " query");
6255         AskQuestion(realname, buf2, buf1, cps->pr);
6256         return;
6257     }
6258     /* Commands from the engine directly to ICS.  We don't allow these to be 
6259      *  sent until we are logged on. Crafty kibitzes have been known to 
6260      *  interfere with the login process.
6261      */
6262     if (loggedOn) {
6263         if (!strncmp(message, "tellics ", 8)) {
6264             SendToICS(message + 8);
6265             SendToICS("\n");
6266             return;
6267         }
6268         if (!strncmp(message, "tellicsnoalias ", 15)) {
6269             SendToICS(ics_prefix);
6270             SendToICS(message + 15);
6271             SendToICS("\n");
6272             return;
6273         }
6274         /* The following are for backward compatibility only */
6275         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6276             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6277             SendToICS(ics_prefix);
6278             SendToICS(message);
6279             SendToICS("\n");
6280             return;
6281         }
6282     }
6283     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6284         return;
6285     }
6286     /*
6287      * If the move is illegal, cancel it and redraw the board.
6288      * Also deal with other error cases.  Matching is rather loose
6289      * here to accommodate engines written before the spec.
6290      */
6291     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6292         strncmp(message, "Error", 5) == 0) {
6293         if (StrStr(message, "name") || 
6294             StrStr(message, "rating") || StrStr(message, "?") ||
6295             StrStr(message, "result") || StrStr(message, "board") ||
6296             StrStr(message, "bk") || StrStr(message, "computer") ||
6297             StrStr(message, "variant") || StrStr(message, "hint") ||
6298             StrStr(message, "random") || StrStr(message, "depth") ||
6299             StrStr(message, "accepted")) {
6300             return;
6301         }
6302         if (StrStr(message, "protover")) {
6303           /* Program is responding to input, so it's apparently done
6304              initializing, and this error message indicates it is
6305              protocol version 1.  So we don't need to wait any longer
6306              for it to initialize and send feature commands. */
6307           FeatureDone(cps, 1);
6308           cps->protocolVersion = 1;
6309           return;
6310         }
6311         cps->maybeThinking = FALSE;
6312
6313         if (StrStr(message, "draw")) {
6314             /* Program doesn't have "draw" command */
6315             cps->sendDrawOffers = 0;
6316             return;
6317         }
6318         if (cps->sendTime != 1 &&
6319             (StrStr(message, "time") || StrStr(message, "otim"))) {
6320           /* Program apparently doesn't have "time" or "otim" command */
6321           cps->sendTime = 0;
6322           return;
6323         }
6324         if (StrStr(message, "analyze")) {
6325             cps->analysisSupport = FALSE;
6326             cps->analyzing = FALSE;
6327             Reset(FALSE, TRUE);
6328             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6329             DisplayError(buf2, 0);
6330             return;
6331         }
6332         if (StrStr(message, "(no matching move)st")) {
6333           /* Special kludge for GNU Chess 4 only */
6334           cps->stKludge = TRUE;
6335           SendTimeControl(cps, movesPerSession, timeControl,
6336                           timeIncrement, appData.searchDepth,
6337                           searchTime);
6338           return;
6339         }
6340         if (StrStr(message, "(no matching move)sd")) {
6341           /* Special kludge for GNU Chess 4 only */
6342           cps->sdKludge = TRUE;
6343           SendTimeControl(cps, movesPerSession, timeControl,
6344                           timeIncrement, appData.searchDepth,
6345                           searchTime);
6346           return;
6347         }
6348         if (!StrStr(message, "llegal")) {
6349             return;
6350         }
6351         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6352             gameMode == IcsIdle) return;
6353         if (forwardMostMove <= backwardMostMove) return;
6354 #if 0
6355         /* Following removed: it caused a bug where a real illegal move
6356            message in analyze mored would be ignored. */
6357         if (cps == &first && programStats.ok_to_send == 0) {
6358             /* Bogus message from Crafty responding to "."  This filtering
6359                can miss some of the bad messages, but fortunately the bug 
6360                is fixed in current Crafty versions, so it doesn't matter. */
6361             return;
6362         }
6363 #endif
6364         if (pausing) PauseEvent();
6365         if (gameMode == PlayFromGameFile) {
6366             /* Stop reading this game file */
6367             gameMode = EditGame;
6368             ModeHighlight();
6369         }
6370         currentMove = --forwardMostMove;
6371         DisplayMove(currentMove-1); /* before DisplayMoveError */
6372         SwitchClocks();
6373         DisplayBothClocks();
6374         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6375                 parseList[currentMove], cps->which);
6376         DisplayMoveError(buf1);
6377         DrawPosition(FALSE, boards[currentMove]);
6378
6379         /* [HGM] illegal-move claim should forfeit game when Xboard */
6380         /* only passes fully legal moves                            */
6381         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6382             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6383                                 "False illegal-move claim", GE_XBOARD );
6384         }
6385         return;
6386     }
6387     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6388         /* Program has a broken "time" command that
6389            outputs a string not ending in newline.
6390            Don't use it. */
6391         cps->sendTime = 0;
6392     }
6393     
6394     /*
6395      * If chess program startup fails, exit with an error message.
6396      * Attempts to recover here are futile.
6397      */
6398     if ((StrStr(message, "unknown host") != NULL)
6399         || (StrStr(message, "No remote directory") != NULL)
6400         || (StrStr(message, "not found") != NULL)
6401         || (StrStr(message, "No such file") != NULL)
6402         || (StrStr(message, "can't alloc") != NULL)
6403         || (StrStr(message, "Permission denied") != NULL)) {
6404
6405         cps->maybeThinking = FALSE;
6406         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6407                 cps->which, cps->program, cps->host, message);
6408         RemoveInputSource(cps->isr);
6409         DisplayFatalError(buf1, 0, 1);
6410         return;
6411     }
6412     
6413     /* 
6414      * Look for hint output
6415      */
6416     if (sscanf(message, "Hint: %s", buf1) == 1) {
6417         if (cps == &first && hintRequested) {
6418             hintRequested = FALSE;
6419             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6420                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6421                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6422                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6423                                     fromY, fromX, toY, toX, promoChar, buf1);
6424                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6425                 DisplayInformation(buf2);
6426             } else {
6427                 /* Hint move could not be parsed!? */
6428               snprintf(buf2, sizeof(buf2),
6429                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6430                         buf1, cps->which);
6431                 DisplayError(buf2, 0);
6432             }
6433         } else {
6434             strcpy(lastHint, buf1);
6435         }
6436         return;
6437     }
6438
6439     /*
6440      * Ignore other messages if game is not in progress
6441      */
6442     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6443         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6444
6445     /*
6446      * look for win, lose, draw, or draw offer
6447      */
6448     if (strncmp(message, "1-0", 3) == 0) {
6449         char *p, *q, *r = "";
6450         p = strchr(message, '{');
6451         if (p) {
6452             q = strchr(p, '}');
6453             if (q) {
6454                 *q = NULLCHAR;
6455                 r = p + 1;
6456             }
6457         }
6458         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6459         return;
6460     } else if (strncmp(message, "0-1", 3) == 0) {
6461         char *p, *q, *r = "";
6462         p = strchr(message, '{');
6463         if (p) {
6464             q = strchr(p, '}');
6465             if (q) {
6466                 *q = NULLCHAR;
6467                 r = p + 1;
6468             }
6469         }
6470         /* Kludge for Arasan 4.1 bug */
6471         if (strcmp(r, "Black resigns") == 0) {
6472             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6473             return;
6474         }
6475         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6476         return;
6477     } else if (strncmp(message, "1/2", 3) == 0) {
6478         char *p, *q, *r = "";
6479         p = strchr(message, '{');
6480         if (p) {
6481             q = strchr(p, '}');
6482             if (q) {
6483                 *q = NULLCHAR;
6484                 r = p + 1;
6485             }
6486         }
6487             
6488         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6489         return;
6490
6491     } else if (strncmp(message, "White resign", 12) == 0) {
6492         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6493         return;
6494     } else if (strncmp(message, "Black resign", 12) == 0) {
6495         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6496         return;
6497     } else if (strncmp(message, "White matches", 13) == 0 ||
6498                strncmp(message, "Black matches", 13) == 0   ) {
6499         /* [HGM] ignore GNUShogi noises */
6500         return;
6501     } else if (strncmp(message, "White", 5) == 0 &&
6502                message[5] != '(' &&
6503                StrStr(message, "Black") == NULL) {
6504         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6505         return;
6506     } else if (strncmp(message, "Black", 5) == 0 &&
6507                message[5] != '(') {
6508         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6509         return;
6510     } else if (strcmp(message, "resign") == 0 ||
6511                strcmp(message, "computer resigns") == 0) {
6512         switch (gameMode) {
6513           case MachinePlaysBlack:
6514           case IcsPlayingBlack:
6515             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6516             break;
6517           case MachinePlaysWhite:
6518           case IcsPlayingWhite:
6519             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6520             break;
6521           case TwoMachinesPlay:
6522             if (cps->twoMachinesColor[0] == 'w')
6523               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6524             else
6525               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6526             break;
6527           default:
6528             /* can't happen */
6529             break;
6530         }
6531         return;
6532     } else if (strncmp(message, "opponent mates", 14) == 0) {
6533         switch (gameMode) {
6534           case MachinePlaysBlack:
6535           case IcsPlayingBlack:
6536             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6537             break;
6538           case MachinePlaysWhite:
6539           case IcsPlayingWhite:
6540             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6541             break;
6542           case TwoMachinesPlay:
6543             if (cps->twoMachinesColor[0] == 'w')
6544               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6545             else
6546               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6547             break;
6548           default:
6549             /* can't happen */
6550             break;
6551         }
6552         return;
6553     } else if (strncmp(message, "computer mates", 14) == 0) {
6554         switch (gameMode) {
6555           case MachinePlaysBlack:
6556           case IcsPlayingBlack:
6557             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6558             break;
6559           case MachinePlaysWhite:
6560           case IcsPlayingWhite:
6561             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6562             break;
6563           case TwoMachinesPlay:
6564             if (cps->twoMachinesColor[0] == 'w')
6565               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6566             else
6567               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6568             break;
6569           default:
6570             /* can't happen */
6571             break;
6572         }
6573         return;
6574     } else if (strncmp(message, "checkmate", 9) == 0) {
6575         if (WhiteOnMove(forwardMostMove)) {
6576             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6577         } else {
6578             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6579         }
6580         return;
6581     } else if (strstr(message, "Draw") != NULL ||
6582                strstr(message, "game is a draw") != NULL) {
6583         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6584         return;
6585     } else if (strstr(message, "offer") != NULL &&
6586                strstr(message, "draw") != NULL) {
6587 #if ZIPPY
6588         if (appData.zippyPlay && first.initDone) {
6589             /* Relay offer to ICS */
6590             SendToICS(ics_prefix);
6591             SendToICS("draw\n");
6592         }
6593 #endif
6594         cps->offeredDraw = 2; /* valid until this engine moves twice */
6595         if (gameMode == TwoMachinesPlay) {
6596             if (cps->other->offeredDraw) {
6597                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6598             /* [HGM] in two-machine mode we delay relaying draw offer      */
6599             /* until after we also have move, to see if it is really claim */
6600             }
6601 #if 0
6602               else {
6603                 if (cps->other->sendDrawOffers) {
6604                     SendToProgram("draw\n", cps->other);
6605                 }
6606             }
6607 #endif
6608         } else if (gameMode == MachinePlaysWhite ||
6609                    gameMode == MachinePlaysBlack) {
6610           if (userOfferedDraw) {
6611             DisplayInformation(_("Machine accepts your draw offer"));
6612             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6613           } else {
6614             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6615           }
6616         }
6617     }
6618
6619     
6620     /*
6621      * Look for thinking output
6622      */
6623     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6624           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6625                                 ) {
6626         int plylev, mvleft, mvtot, curscore, time;
6627         char mvname[MOVE_LEN];
6628         u64 nodes; // [DM]
6629         char plyext;
6630         int ignore = FALSE;
6631         int prefixHint = FALSE;
6632         mvname[0] = NULLCHAR;
6633
6634         switch (gameMode) {
6635           case MachinePlaysBlack:
6636           case IcsPlayingBlack:
6637             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6638             break;
6639           case MachinePlaysWhite:
6640           case IcsPlayingWhite:
6641             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6642             break;
6643           case AnalyzeMode:
6644           case AnalyzeFile:
6645             break;
6646           case IcsObserving: /* [DM] icsEngineAnalyze */
6647             if (!appData.icsEngineAnalyze) ignore = TRUE;
6648             break;
6649           case TwoMachinesPlay:
6650             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6651                 ignore = TRUE;
6652             }
6653             break;
6654           default:
6655             ignore = TRUE;
6656             break;
6657         }
6658
6659         if (!ignore) {
6660             buf1[0] = NULLCHAR;
6661             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6662                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6663
6664                 if (plyext != ' ' && plyext != '\t') {
6665                     time *= 100;
6666                 }
6667
6668                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6669                 if( cps->scoreIsAbsolute && 
6670                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6671                 {
6672                     curscore = -curscore;
6673                 }
6674
6675
6676                 programStats.depth = plylev;
6677                 programStats.nodes = nodes;
6678                 programStats.time = time;
6679                 programStats.score = curscore;
6680                 programStats.got_only_move = 0;
6681
6682                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6683                         int ticklen;
6684
6685                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6686                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6687                         if(WhiteOnMove(forwardMostMove)) 
6688                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6689                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6690                 }
6691
6692                 /* Buffer overflow protection */
6693                 if (buf1[0] != NULLCHAR) {
6694                     if (strlen(buf1) >= sizeof(programStats.movelist)
6695                         && appData.debugMode) {
6696                         fprintf(debugFP,
6697                                 "PV is too long; using the first %d bytes.\n",
6698                                 sizeof(programStats.movelist) - 1);
6699                     }
6700
6701                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6702                 } else {
6703                     sprintf(programStats.movelist, " no PV\n");
6704                 }
6705
6706                 if (programStats.seen_stat) {
6707                     programStats.ok_to_send = 1;
6708                 }
6709
6710                 if (strchr(programStats.movelist, '(') != NULL) {
6711                     programStats.line_is_book = 1;
6712                     programStats.nr_moves = 0;
6713                     programStats.moves_left = 0;
6714                 } else {
6715                     programStats.line_is_book = 0;
6716                 }
6717
6718                 SendProgramStatsToFrontend( cps, &programStats );
6719
6720                 /* 
6721                     [AS] Protect the thinkOutput buffer from overflow... this
6722                     is only useful if buf1 hasn't overflowed first!
6723                 */
6724                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6725                         plylev, 
6726                         (gameMode == TwoMachinesPlay ?
6727                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6728                         ((double) curscore) / 100.0,
6729                         prefixHint ? lastHint : "",
6730                         prefixHint ? " " : "" );
6731
6732                 if( buf1[0] != NULLCHAR ) {
6733                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6734
6735                     if( strlen(buf1) > max_len ) {
6736                         if( appData.debugMode) {
6737                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6738                         }
6739                         buf1[max_len+1] = '\0';
6740                     }
6741
6742                     strcat( thinkOutput, buf1 );
6743                 }
6744
6745                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6746                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6747                     DisplayMove(currentMove - 1);
6748                     DisplayAnalysis();
6749                 }
6750                 return;
6751
6752             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6753                 /* crafty (9.25+) says "(only move) <move>"
6754                  * if there is only 1 legal move
6755                  */
6756                 sscanf(p, "(only move) %s", buf1);
6757                 sprintf(thinkOutput, "%s (only move)", buf1);
6758                 sprintf(programStats.movelist, "%s (only move)", buf1);
6759                 programStats.depth = 1;
6760                 programStats.nr_moves = 1;
6761                 programStats.moves_left = 1;
6762                 programStats.nodes = 1;
6763                 programStats.time = 1;
6764                 programStats.got_only_move = 1;
6765
6766                 /* Not really, but we also use this member to
6767                    mean "line isn't going to change" (Crafty
6768                    isn't searching, so stats won't change) */
6769                 programStats.line_is_book = 1;
6770
6771                 SendProgramStatsToFrontend( cps, &programStats );
6772                 
6773                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
6774                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6775                     DisplayMove(currentMove - 1);
6776                     DisplayAnalysis();
6777                 }
6778                 return;
6779             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6780                               &time, &nodes, &plylev, &mvleft,
6781                               &mvtot, mvname) >= 5) {
6782                 /* The stat01: line is from Crafty (9.29+) in response
6783                    to the "." command */
6784                 programStats.seen_stat = 1;
6785                 cps->maybeThinking = TRUE;
6786
6787                 if (programStats.got_only_move || !appData.periodicUpdates)
6788                   return;
6789
6790                 programStats.depth = plylev;
6791                 programStats.time = time;
6792                 programStats.nodes = nodes;
6793                 programStats.moves_left = mvleft;
6794                 programStats.nr_moves = mvtot;
6795                 strcpy(programStats.move_name, mvname);
6796                 programStats.ok_to_send = 1;
6797                 programStats.movelist[0] = '\0';
6798
6799                 SendProgramStatsToFrontend( cps, &programStats );
6800
6801                 DisplayAnalysis();
6802                 return;
6803
6804             } else if (strncmp(message,"++",2) == 0) {
6805                 /* Crafty 9.29+ outputs this */
6806                 programStats.got_fail = 2;
6807                 return;
6808
6809             } else if (strncmp(message,"--",2) == 0) {
6810                 /* Crafty 9.29+ outputs this */
6811                 programStats.got_fail = 1;
6812                 return;
6813
6814             } else if (thinkOutput[0] != NULLCHAR &&
6815                        strncmp(message, "    ", 4) == 0) {
6816                 unsigned message_len;
6817
6818                 p = message;
6819                 while (*p && *p == ' ') p++;
6820
6821                 message_len = strlen( p );
6822
6823                 /* [AS] Avoid buffer overflow */
6824                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6825                     strcat(thinkOutput, " ");
6826                     strcat(thinkOutput, p);
6827                 }
6828
6829                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6830                     strcat(programStats.movelist, " ");
6831                     strcat(programStats.movelist, p);
6832                 }
6833
6834                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6835                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6836                     DisplayMove(currentMove - 1);
6837                     DisplayAnalysis();
6838                 }
6839                 return;
6840             }
6841         }
6842         else {
6843             buf1[0] = NULLCHAR;
6844
6845             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6846                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
6847             {
6848                 ChessProgramStats cpstats;
6849
6850                 if (plyext != ' ' && plyext != '\t') {
6851                     time *= 100;
6852                 }
6853
6854                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6855                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6856                     curscore = -curscore;
6857                 }
6858
6859                 cpstats.depth = plylev;
6860                 cpstats.nodes = nodes;
6861                 cpstats.time = time;
6862                 cpstats.score = curscore;
6863                 cpstats.got_only_move = 0;
6864                 cpstats.movelist[0] = '\0';
6865
6866                 if (buf1[0] != NULLCHAR) {
6867                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6868                 }
6869
6870                 cpstats.ok_to_send = 0;
6871                 cpstats.line_is_book = 0;
6872                 cpstats.nr_moves = 0;
6873                 cpstats.moves_left = 0;
6874
6875                 SendProgramStatsToFrontend( cps, &cpstats );
6876             }
6877         }
6878     }
6879 }
6880
6881
6882 /* Parse a game score from the character string "game", and
6883    record it as the history of the current game.  The game
6884    score is NOT assumed to start from the standard position. 
6885    The display is not updated in any way.
6886    */
6887 void
6888 ParseGameHistory(game)
6889      char *game;
6890 {
6891     ChessMove moveType;
6892     int fromX, fromY, toX, toY, boardIndex;
6893     char promoChar;
6894     char *p, *q;
6895     char buf[MSG_SIZ];
6896
6897     if (appData.debugMode)
6898       fprintf(debugFP, "Parsing game history: %s\n", game);
6899
6900     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6901     gameInfo.site = StrSave(appData.icsHost);
6902     gameInfo.date = PGNDate();
6903     gameInfo.round = StrSave("-");
6904
6905     /* Parse out names of players */
6906     while (*game == ' ') game++;
6907     p = buf;
6908     while (*game != ' ') *p++ = *game++;
6909     *p = NULLCHAR;
6910     gameInfo.white = StrSave(buf);
6911     while (*game == ' ') game++;
6912     p = buf;
6913     while (*game != ' ' && *game != '\n') *p++ = *game++;
6914     *p = NULLCHAR;
6915     gameInfo.black = StrSave(buf);
6916
6917     /* Parse moves */
6918     boardIndex = blackPlaysFirst ? 1 : 0;
6919     yynewstr(game);
6920     for (;;) {
6921         yyboardindex = boardIndex;
6922         moveType = (ChessMove) yylex();
6923         switch (moveType) {
6924           case IllegalMove:             /* maybe suicide chess, etc. */
6925   if (appData.debugMode) {
6926     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6927     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6928     setbuf(debugFP, NULL);
6929   }
6930           case WhitePromotionChancellor:
6931           case BlackPromotionChancellor:
6932           case WhitePromotionArchbishop:
6933           case BlackPromotionArchbishop:
6934           case WhitePromotionQueen:
6935           case BlackPromotionQueen:
6936           case WhitePromotionRook:
6937           case BlackPromotionRook:
6938           case WhitePromotionBishop:
6939           case BlackPromotionBishop:
6940           case WhitePromotionKnight:
6941           case BlackPromotionKnight:
6942           case WhitePromotionKing:
6943           case BlackPromotionKing:
6944           case NormalMove:
6945           case WhiteCapturesEnPassant:
6946           case BlackCapturesEnPassant:
6947           case WhiteKingSideCastle:
6948           case WhiteQueenSideCastle:
6949           case BlackKingSideCastle:
6950           case BlackQueenSideCastle:
6951           case WhiteKingSideCastleWild:
6952           case WhiteQueenSideCastleWild:
6953           case BlackKingSideCastleWild:
6954           case BlackQueenSideCastleWild:
6955           /* PUSH Fabien */
6956           case WhiteHSideCastleFR:
6957           case WhiteASideCastleFR:
6958           case BlackHSideCastleFR:
6959           case BlackASideCastleFR:
6960           /* POP Fabien */
6961             fromX = currentMoveString[0] - AAA;
6962             fromY = currentMoveString[1] - ONE;
6963             toX = currentMoveString[2] - AAA;
6964             toY = currentMoveString[3] - ONE;
6965             promoChar = currentMoveString[4];
6966             break;
6967           case WhiteDrop:
6968           case BlackDrop:
6969             fromX = moveType == WhiteDrop ?
6970               (int) CharToPiece(ToUpper(currentMoveString[0])) :
6971             (int) CharToPiece(ToLower(currentMoveString[0]));
6972             fromY = DROP_RANK;
6973             toX = currentMoveString[2] - AAA;
6974             toY = currentMoveString[3] - ONE;
6975             promoChar = NULLCHAR;
6976             break;
6977           case AmbiguousMove:
6978             /* bug? */
6979             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6980   if (appData.debugMode) {
6981     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6982     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6983     setbuf(debugFP, NULL);
6984   }
6985             DisplayError(buf, 0);
6986             return;
6987           case ImpossibleMove:
6988             /* bug? */
6989             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6990   if (appData.debugMode) {
6991     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6992     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6993     setbuf(debugFP, NULL);
6994   }
6995             DisplayError(buf, 0);
6996             return;
6997           case (ChessMove) 0:   /* end of file */
6998             if (boardIndex < backwardMostMove) {
6999                 /* Oops, gap.  How did that happen? */
7000                 DisplayError(_("Gap in move list"), 0);
7001                 return;
7002             }
7003             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7004             if (boardIndex > forwardMostMove) {
7005                 forwardMostMove = boardIndex;
7006             }
7007             return;
7008           case ElapsedTime:
7009             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7010                 strcat(parseList[boardIndex-1], " ");
7011                 strcat(parseList[boardIndex-1], yy_text);
7012             }
7013             continue;
7014           case Comment:
7015           case PGNTag:
7016           case NAG:
7017           default:
7018             /* ignore */
7019             continue;
7020           case WhiteWins:
7021           case BlackWins:
7022           case GameIsDrawn:
7023           case GameUnfinished:
7024             if (gameMode == IcsExamining) {
7025                 if (boardIndex < backwardMostMove) {
7026                     /* Oops, gap.  How did that happen? */
7027                     return;
7028                 }
7029                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7030                 return;
7031             }
7032             gameInfo.result = moveType;
7033             p = strchr(yy_text, '{');
7034             if (p == NULL) p = strchr(yy_text, '(');
7035             if (p == NULL) {
7036                 p = yy_text;
7037                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7038             } else {
7039                 q = strchr(p, *p == '{' ? '}' : ')');
7040                 if (q != NULL) *q = NULLCHAR;
7041                 p++;
7042             }
7043             gameInfo.resultDetails = StrSave(p);
7044             continue;
7045         }
7046         if (boardIndex >= forwardMostMove &&
7047             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7048             backwardMostMove = blackPlaysFirst ? 1 : 0;
7049             return;
7050         }
7051         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7052                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7053                                  parseList[boardIndex]);
7054         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7055         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7056         /* currentMoveString is set as a side-effect of yylex */
7057         strcpy(moveList[boardIndex], currentMoveString);
7058         strcat(moveList[boardIndex], "\n");
7059         boardIndex++;
7060         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], 
7061                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7062         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7063                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7064           case MT_NONE:
7065           case MT_STALEMATE:
7066           default:
7067             break;
7068           case MT_CHECK:
7069             if(gameInfo.variant != VariantShogi)
7070                 strcat(parseList[boardIndex - 1], "+");
7071             break;
7072           case MT_CHECKMATE:
7073           case MT_STAINMATE:
7074             strcat(parseList[boardIndex - 1], "#");
7075             break;
7076         }
7077     }
7078 }
7079
7080
7081 /* Apply a move to the given board  */
7082 void
7083 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7084      int fromX, fromY, toX, toY;
7085      int promoChar;
7086      Board board;
7087      char *castling;
7088      char *ep;
7089 {
7090   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7091
7092     /* [HGM] compute & store e.p. status and castling rights for new position */
7093     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7094     { int i;
7095
7096       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7097       oldEP = *ep;
7098       *ep = EP_NONE;
7099
7100       if( board[toY][toX] != EmptySquare ) 
7101            *ep = EP_CAPTURE;  
7102
7103       if( board[fromY][fromX] == WhitePawn ) {
7104            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7105                *ep = EP_PAWN_MOVE;
7106            if( toY-fromY==2) {
7107                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7108                         gameInfo.variant != VariantBerolina || toX < fromX)
7109                       *ep = toX | berolina;
7110                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7111                         gameInfo.variant != VariantBerolina || toX > fromX) 
7112                       *ep = toX;
7113            }
7114       } else 
7115       if( board[fromY][fromX] == BlackPawn ) {
7116            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7117                *ep = EP_PAWN_MOVE; 
7118            if( toY-fromY== -2) {
7119                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7120                         gameInfo.variant != VariantBerolina || toX < fromX)
7121                       *ep = toX | berolina;
7122                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7123                         gameInfo.variant != VariantBerolina || toX > fromX) 
7124                       *ep = toX;
7125            }
7126        }
7127
7128        for(i=0; i<nrCastlingRights; i++) {
7129            if(castling[i] == fromX && castlingRank[i] == fromY ||
7130               castling[i] == toX   && castlingRank[i] == toY   
7131              ) castling[i] = -1; // revoke for moved or captured piece
7132        }
7133
7134     }
7135
7136   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7137   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7138        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7139          
7140   if (fromX == toX && fromY == toY) return;
7141
7142   if (fromY == DROP_RANK) {
7143         /* must be first */
7144         piece = board[toY][toX] = (ChessSquare) fromX;
7145   } else {
7146      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7147      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7148      if(gameInfo.variant == VariantKnightmate)
7149          king += (int) WhiteUnicorn - (int) WhiteKing;
7150
7151     /* Code added by Tord: */
7152     /* FRC castling assumed when king captures friendly rook. */
7153     if (board[fromY][fromX] == WhiteKing &&
7154              board[toY][toX] == WhiteRook) {
7155       board[fromY][fromX] = EmptySquare;
7156       board[toY][toX] = EmptySquare;
7157       if(toX > fromX) {
7158         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7159       } else {
7160         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7161       }
7162     } else if (board[fromY][fromX] == BlackKing &&
7163                board[toY][toX] == BlackRook) {
7164       board[fromY][fromX] = EmptySquare;
7165       board[toY][toX] = EmptySquare;
7166       if(toX > fromX) {
7167         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7168       } else {
7169         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7170       }
7171     /* End of code added by Tord */
7172
7173     } else if (board[fromY][fromX] == king
7174         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7175         && toY == fromY && toX > fromX+1) {
7176         board[fromY][fromX] = EmptySquare;
7177         board[toY][toX] = king;
7178         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7179         board[fromY][BOARD_RGHT-1] = EmptySquare;
7180     } else if (board[fromY][fromX] == king
7181         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7182                && toY == fromY && toX < fromX-1) {
7183         board[fromY][fromX] = EmptySquare;
7184         board[toY][toX] = king;
7185         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7186         board[fromY][BOARD_LEFT] = EmptySquare;
7187     } else if (board[fromY][fromX] == WhitePawn
7188                && toY == BOARD_HEIGHT-1
7189                && gameInfo.variant != VariantXiangqi
7190                ) {
7191         /* white pawn promotion */
7192         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7193         if (board[toY][toX] == EmptySquare) {
7194             board[toY][toX] = WhiteQueen;
7195         }
7196         if(gameInfo.variant==VariantBughouse ||
7197            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7198             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7199         board[fromY][fromX] = EmptySquare;
7200     } else if ((fromY == BOARD_HEIGHT-4)
7201                && (toX != fromX)
7202                && gameInfo.variant != VariantXiangqi
7203                && gameInfo.variant != VariantBerolina
7204                && (board[fromY][fromX] == WhitePawn)
7205                && (board[toY][toX] == EmptySquare)) {
7206         board[fromY][fromX] = EmptySquare;
7207         board[toY][toX] = WhitePawn;
7208         captured = board[toY - 1][toX];
7209         board[toY - 1][toX] = EmptySquare;
7210     } else if ((fromY == BOARD_HEIGHT-4)
7211                && (toX == fromX)
7212                && gameInfo.variant == VariantBerolina
7213                && (board[fromY][fromX] == WhitePawn)
7214                && (board[toY][toX] == EmptySquare)) {
7215         board[fromY][fromX] = EmptySquare;
7216         board[toY][toX] = WhitePawn;
7217         if(oldEP & EP_BEROLIN_A) {
7218                 captured = board[fromY][fromX-1];
7219                 board[fromY][fromX-1] = EmptySquare;
7220         }else{  captured = board[fromY][fromX+1];
7221                 board[fromY][fromX+1] = EmptySquare;
7222         }
7223     } else if (board[fromY][fromX] == king
7224         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7225                && toY == fromY && toX > fromX+1) {
7226         board[fromY][fromX] = EmptySquare;
7227         board[toY][toX] = king;
7228         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7229         board[fromY][BOARD_RGHT-1] = EmptySquare;
7230     } else if (board[fromY][fromX] == king
7231         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7232                && toY == fromY && toX < fromX-1) {
7233         board[fromY][fromX] = EmptySquare;
7234         board[toY][toX] = king;
7235         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7236         board[fromY][BOARD_LEFT] = EmptySquare;
7237     } else if (fromY == 7 && fromX == 3
7238                && board[fromY][fromX] == BlackKing
7239                && toY == 7 && toX == 5) {
7240         board[fromY][fromX] = EmptySquare;
7241         board[toY][toX] = BlackKing;
7242         board[fromY][7] = EmptySquare;
7243         board[toY][4] = BlackRook;
7244     } else if (fromY == 7 && fromX == 3
7245                && board[fromY][fromX] == BlackKing
7246                && toY == 7 && toX == 1) {
7247         board[fromY][fromX] = EmptySquare;
7248         board[toY][toX] = BlackKing;
7249         board[fromY][0] = EmptySquare;
7250         board[toY][2] = BlackRook;
7251     } else if (board[fromY][fromX] == BlackPawn
7252                && toY == 0
7253                && gameInfo.variant != VariantXiangqi
7254                ) {
7255         /* black pawn promotion */
7256         board[0][toX] = CharToPiece(ToLower(promoChar));
7257         if (board[0][toX] == EmptySquare) {
7258             board[0][toX] = BlackQueen;
7259         }
7260         if(gameInfo.variant==VariantBughouse ||
7261            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7262             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7263         board[fromY][fromX] = EmptySquare;
7264     } else if ((fromY == 3)
7265                && (toX != fromX)
7266                && gameInfo.variant != VariantXiangqi
7267                && gameInfo.variant != VariantBerolina
7268                && (board[fromY][fromX] == BlackPawn)
7269                && (board[toY][toX] == EmptySquare)) {
7270         board[fromY][fromX] = EmptySquare;
7271         board[toY][toX] = BlackPawn;
7272         captured = board[toY + 1][toX];
7273         board[toY + 1][toX] = EmptySquare;
7274     } else if ((fromY == 3)
7275                && (toX == fromX)
7276                && gameInfo.variant == VariantBerolina
7277                && (board[fromY][fromX] == BlackPawn)
7278                && (board[toY][toX] == EmptySquare)) {
7279         board[fromY][fromX] = EmptySquare;
7280         board[toY][toX] = BlackPawn;
7281         if(oldEP & EP_BEROLIN_A) {
7282                 captured = board[fromY][fromX-1];
7283                 board[fromY][fromX-1] = EmptySquare;
7284         }else{  captured = board[fromY][fromX+1];
7285                 board[fromY][fromX+1] = EmptySquare;
7286         }
7287     } else {
7288         board[toY][toX] = board[fromY][fromX];
7289         board[fromY][fromX] = EmptySquare;
7290     }
7291
7292     /* [HGM] now we promote for Shogi, if needed */
7293     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7294         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7295   }
7296
7297     if (gameInfo.holdingsWidth != 0) {
7298
7299       /* !!A lot more code needs to be written to support holdings  */
7300       /* [HGM] OK, so I have written it. Holdings are stored in the */
7301       /* penultimate board files, so they are automaticlly stored   */
7302       /* in the game history.                                       */
7303       if (fromY == DROP_RANK) {
7304         /* Delete from holdings, by decreasing count */
7305         /* and erasing image if necessary            */
7306         p = (int) fromX;
7307         if(p < (int) BlackPawn) { /* white drop */
7308              p -= (int)WhitePawn;
7309              if(p >= gameInfo.holdingsSize) p = 0;
7310              if(--board[p][BOARD_WIDTH-2] == 0)
7311                   board[p][BOARD_WIDTH-1] = EmptySquare;
7312         } else {                  /* black drop */
7313              p -= (int)BlackPawn;
7314              if(p >= gameInfo.holdingsSize) p = 0;
7315              if(--board[BOARD_HEIGHT-1-p][1] == 0)
7316                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7317         }
7318       }
7319       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7320           && gameInfo.variant != VariantBughouse        ) {
7321         /* [HGM] holdings: Add to holdings, if holdings exist */
7322         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7323                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7324                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7325         }
7326         p = (int) captured;
7327         if (p >= (int) BlackPawn) {
7328           p -= (int)BlackPawn;
7329           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7330                   /* in Shogi restore piece to its original  first */
7331                   captured = (ChessSquare) (DEMOTED captured);
7332                   p = DEMOTED p;
7333           }
7334           p = PieceToNumber((ChessSquare)p);
7335           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7336           board[p][BOARD_WIDTH-2]++;
7337           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7338         } else {
7339           p -= (int)WhitePawn;
7340           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7341                   captured = (ChessSquare) (DEMOTED captured);
7342                   p = DEMOTED p;
7343           }
7344           p = PieceToNumber((ChessSquare)p);
7345           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7346           board[BOARD_HEIGHT-1-p][1]++;
7347           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7348         }
7349       }
7350
7351     } else if (gameInfo.variant == VariantAtomic) {
7352       if (captured != EmptySquare) {
7353         int y, x;
7354         for (y = toY-1; y <= toY+1; y++) {
7355           for (x = toX-1; x <= toX+1; x++) {
7356             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7357                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7358               board[y][x] = EmptySquare;
7359             }
7360           }
7361         }
7362         board[toY][toX] = EmptySquare;
7363       }
7364     }
7365     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7366         /* [HGM] Shogi promotions */
7367         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7368     }
7369
7370     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7371                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7372         // [HGM] superchess: take promotion piece out of holdings
7373         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7374         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7375             if(!--board[k][BOARD_WIDTH-2])
7376                 board[k][BOARD_WIDTH-1] = EmptySquare;
7377         } else {
7378             if(!--board[BOARD_HEIGHT-1-k][1])
7379                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7380         }
7381     }
7382
7383 }
7384
7385 /* Updates forwardMostMove */
7386 void
7387 MakeMove(fromX, fromY, toX, toY, promoChar)
7388      int fromX, fromY, toX, toY;
7389      int promoChar;
7390 {
7391 //    forwardMostMove++; // [HGM] bare: moved downstream
7392
7393     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7394         int timeLeft; static int lastLoadFlag=0; int king, piece;
7395         piece = boards[forwardMostMove][fromY][fromX];
7396         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7397         if(gameInfo.variant == VariantKnightmate)
7398             king += (int) WhiteUnicorn - (int) WhiteKing;
7399         if(forwardMostMove == 0) {
7400             if(blackPlaysFirst) 
7401                 fprintf(serverMoves, "%s;", second.tidy);
7402             fprintf(serverMoves, "%s;", first.tidy);
7403             if(!blackPlaysFirst) 
7404                 fprintf(serverMoves, "%s;", second.tidy);
7405         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7406         lastLoadFlag = loadFlag;
7407         // print base move
7408         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7409         // print castling suffix
7410         if( toY == fromY && piece == king ) {
7411             if(toX-fromX > 1)
7412                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7413             if(fromX-toX >1)
7414                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7415         }
7416         // e.p. suffix
7417         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7418              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7419              boards[forwardMostMove][toY][toX] == EmptySquare
7420              && fromX != toX )
7421                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7422         // promotion suffix
7423         if(promoChar != NULLCHAR)
7424                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7425         if(!loadFlag) {
7426             fprintf(serverMoves, "/%d/%d",
7427                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7428             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7429             else                      timeLeft = blackTimeRemaining/1000;
7430             fprintf(serverMoves, "/%d", timeLeft);
7431         }
7432         fflush(serverMoves);
7433     }
7434
7435     if (forwardMostMove+1 >= MAX_MOVES) {
7436       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7437                         0, 1);
7438       return;
7439     }
7440     SwitchClocks();
7441     timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
7442     timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
7443     if (commentList[forwardMostMove+1] != NULL) {
7444         free(commentList[forwardMostMove+1]);
7445         commentList[forwardMostMove+1] = NULL;
7446     }
7447     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7448     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7449     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], 
7450                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7451     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7452     gameInfo.result = GameUnfinished;
7453     if (gameInfo.resultDetails != NULL) {
7454         free(gameInfo.resultDetails);
7455         gameInfo.resultDetails = NULL;
7456     }
7457     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7458                               moveList[forwardMostMove - 1]);
7459     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7460                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7461                              fromY, fromX, toY, toX, promoChar,
7462                              parseList[forwardMostMove - 1]);
7463     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7464                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7465                             castlingRights[forwardMostMove]) ) {
7466       case MT_NONE:
7467       case MT_STALEMATE:
7468       default:
7469         break;
7470       case MT_CHECK:
7471         if(gameInfo.variant != VariantShogi)
7472             strcat(parseList[forwardMostMove - 1], "+");
7473         break;
7474       case MT_CHECKMATE:
7475       case MT_STAINMATE:
7476         strcat(parseList[forwardMostMove - 1], "#");
7477         break;
7478     }
7479     if (appData.debugMode) {
7480         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7481     }
7482
7483 }
7484
7485 /* Updates currentMove if not pausing */
7486 void
7487 ShowMove(fromX, fromY, toX, toY)
7488 {
7489     int instant = (gameMode == PlayFromGameFile) ?
7490         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7491     if(appData.noGUI) return;
7492     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7493         if (!instant) {
7494             if (forwardMostMove == currentMove + 1) {
7495                 AnimateMove(boards[forwardMostMove - 1],
7496                             fromX, fromY, toX, toY);
7497             }
7498             if (appData.highlightLastMove) {
7499                 SetHighlights(fromX, fromY, toX, toY);
7500             }
7501         }
7502         currentMove = forwardMostMove;
7503     }
7504
7505     if (instant) return;
7506
7507     DisplayMove(currentMove - 1);
7508     DrawPosition(FALSE, boards[currentMove]);
7509     DisplayBothClocks();
7510     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7511 }
7512
7513 void SendEgtPath(ChessProgramState *cps)
7514 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7515         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7516
7517         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7518
7519         while(*p) {
7520             char c, *q = name+1, *r, *s;
7521
7522             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7523             while(*p && *p != ',') *q++ = *p++;
7524             *q++ = ':'; *q = 0;
7525             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7526                 strcmp(name, ",nalimov:") == 0 ) {
7527                 // take nalimov path from the menu-changeable option first, if it is defined
7528                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7529                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7530             } else
7531             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7532                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7533                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7534                 s = r = StrStr(s, ":") + 1; // beginning of path info
7535                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7536                 c = *r; *r = 0;             // temporarily null-terminate path info
7537                     *--q = 0;               // strip of trailig ':' from name
7538                     sprintf(buf, "egtbpath %s %s\n", name+1, s);
7539                 *r = c;
7540                 SendToProgram(buf,cps);     // send egtbpath command for this format
7541             }
7542             if(*p == ',') p++; // read away comma to position for next format name
7543         }
7544 }
7545
7546 void
7547 InitChessProgram(cps, setup)
7548      ChessProgramState *cps;
7549      int setup; /* [HGM] needed to setup FRC opening position */
7550 {
7551     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7552     if (appData.noChessProgram) return;
7553     hintRequested = FALSE;
7554     bookRequested = FALSE;
7555
7556     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7557     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7558     if(cps->memSize) { /* [HGM] memory */
7559         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7560         SendToProgram(buf, cps);
7561     }
7562     SendEgtPath(cps); /* [HGM] EGT */
7563     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7564         sprintf(buf, "cores %d\n", appData.smpCores);
7565         SendToProgram(buf, cps);
7566     }
7567
7568     SendToProgram(cps->initString, cps);
7569     if (gameInfo.variant != VariantNormal &&
7570         gameInfo.variant != VariantLoadable
7571         /* [HGM] also send variant if board size non-standard */
7572         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7573                                             ) {
7574       char *v = VariantName(gameInfo.variant);
7575       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7576         /* [HGM] in protocol 1 we have to assume all variants valid */
7577         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7578         DisplayFatalError(buf, 0, 1);
7579         return;
7580       }
7581
7582       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7583       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7584       if( gameInfo.variant == VariantXiangqi )
7585            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7586       if( gameInfo.variant == VariantShogi )
7587            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7588       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7589            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7590       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
7591                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7592            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7593       if( gameInfo.variant == VariantCourier )
7594            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7595       if( gameInfo.variant == VariantSuper )
7596            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7597       if( gameInfo.variant == VariantGreat )
7598            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7599
7600       if(overruled) {
7601            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
7602                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7603            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7604            if(StrStr(cps->variants, b) == NULL) { 
7605                // specific sized variant not known, check if general sizing allowed
7606                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7607                    if(StrStr(cps->variants, "boardsize") == NULL) {
7608                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7609                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7610                        DisplayFatalError(buf, 0, 1);
7611                        return;
7612                    }
7613                    /* [HGM] here we really should compare with the maximum supported board size */
7614                }
7615            }
7616       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7617       sprintf(buf, "variant %s\n", b);
7618       SendToProgram(buf, cps);
7619     }
7620     currentlyInitializedVariant = gameInfo.variant;
7621
7622     /* [HGM] send opening position in FRC to first engine */
7623     if(setup) {
7624           SendToProgram("force\n", cps);
7625           SendBoard(cps, 0);
7626           /* engine is now in force mode! Set flag to wake it up after first move. */
7627           setboardSpoiledMachineBlack = 1;
7628     }
7629
7630     if (cps->sendICS) {
7631       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7632       SendToProgram(buf, cps);
7633     }
7634     cps->maybeThinking = FALSE;
7635     cps->offeredDraw = 0;
7636     if (!appData.icsActive) {
7637         SendTimeControl(cps, movesPerSession, timeControl,
7638                         timeIncrement, appData.searchDepth,
7639                         searchTime);
7640     }
7641     if (appData.showThinking 
7642         // [HGM] thinking: four options require thinking output to be sent
7643         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7644                                 ) {
7645         SendToProgram("post\n", cps);
7646     }
7647     SendToProgram("hard\n", cps);
7648     if (!appData.ponderNextMove) {
7649         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7650            it without being sure what state we are in first.  "hard"
7651            is not a toggle, so that one is OK.
7652          */
7653         SendToProgram("easy\n", cps);
7654     }
7655     if (cps->usePing) {
7656       sprintf(buf, "ping %d\n", ++cps->lastPing);
7657       SendToProgram(buf, cps);
7658     }
7659     cps->initDone = TRUE;
7660 }   
7661
7662
7663 void
7664 StartChessProgram(cps)
7665      ChessProgramState *cps;
7666 {
7667     char buf[MSG_SIZ];
7668     int err;
7669
7670     if (appData.noChessProgram) return;
7671     cps->initDone = FALSE;
7672
7673     if (strcmp(cps->host, "localhost") == 0) {
7674         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7675     } else if (*appData.remoteShell == NULLCHAR) {
7676         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7677     } else {
7678         if (*appData.remoteUser == NULLCHAR) {
7679           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7680                     cps->program);
7681         } else {
7682           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7683                     cps->host, appData.remoteUser, cps->program);
7684         }
7685         err = StartChildProcess(buf, "", &cps->pr);
7686     }
7687     
7688     if (err != 0) {
7689         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7690         DisplayFatalError(buf, err, 1);
7691         cps->pr = NoProc;
7692         cps->isr = NULL;
7693         return;
7694     }
7695     
7696     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7697     if (cps->protocolVersion > 1) {
7698       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7699       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7700       cps->comboCnt = 0;  //                and values of combo boxes
7701       SendToProgram(buf, cps);
7702     } else {
7703       SendToProgram("xboard\n", cps);
7704     }
7705 }
7706
7707
7708 void
7709 TwoMachinesEventIfReady P((void))
7710 {
7711   if (first.lastPing != first.lastPong) {
7712     DisplayMessage("", _("Waiting for first chess program"));
7713     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7714     return;
7715   }
7716   if (second.lastPing != second.lastPong) {
7717     DisplayMessage("", _("Waiting for second chess program"));
7718     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7719     return;
7720   }
7721   ThawUI();
7722   TwoMachinesEvent();
7723 }
7724
7725 void
7726 NextMatchGame P((void))
7727 {
7728     int index; /* [HGM] autoinc: step lod index during match */
7729     Reset(FALSE, TRUE);
7730     if (*appData.loadGameFile != NULLCHAR) {
7731         index = appData.loadGameIndex;
7732         if(index < 0) { // [HGM] autoinc
7733             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7734             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7735         } 
7736         LoadGameFromFile(appData.loadGameFile,
7737                          index,
7738                          appData.loadGameFile, FALSE);
7739     } else if (*appData.loadPositionFile != NULLCHAR) {
7740         index = appData.loadPositionIndex;
7741         if(index < 0) { // [HGM] autoinc
7742             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7743             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7744         } 
7745         LoadPositionFromFile(appData.loadPositionFile,
7746                              index,
7747                              appData.loadPositionFile);
7748     }
7749     TwoMachinesEventIfReady();
7750 }
7751
7752 void UserAdjudicationEvent( int result )
7753 {
7754     ChessMove gameResult = GameIsDrawn;
7755
7756     if( result > 0 ) {
7757         gameResult = WhiteWins;
7758     }
7759     else if( result < 0 ) {
7760         gameResult = BlackWins;
7761     }
7762
7763     if( gameMode == TwoMachinesPlay ) {
7764         GameEnds( gameResult, "User adjudication", GE_XBOARD );
7765     }
7766 }
7767
7768
7769 // [HGM] save: calculate checksum of game to make games easily identifiable
7770 int StringCheckSum(char *s)
7771 {
7772         int i = 0;
7773         if(s==NULL) return 0;
7774         while(*s) i = i*259 + *s++;
7775         return i;
7776 }
7777
7778 int GameCheckSum()
7779 {
7780         int i, sum=0;
7781         for(i=backwardMostMove; i<forwardMostMove; i++) {
7782                 sum += pvInfoList[i].depth;
7783                 sum += StringCheckSum(parseList[i]);
7784                 sum += StringCheckSum(commentList[i]);
7785                 sum *= 261;
7786         }
7787         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7788         return sum + StringCheckSum(commentList[i]);
7789 } // end of save patch
7790
7791 void
7792 GameEnds(result, resultDetails, whosays)
7793      ChessMove result;
7794      char *resultDetails;
7795      int whosays;
7796 {
7797     GameMode nextGameMode;
7798     int isIcsGame;
7799     char buf[MSG_SIZ];
7800
7801     if(endingGame) return; /* [HGM] crash: forbid recursion */
7802     endingGame = 1;
7803
7804     if (appData.debugMode) {
7805       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7806               result, resultDetails ? resultDetails : "(null)", whosays);
7807     }
7808
7809     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7810         /* If we are playing on ICS, the server decides when the
7811            game is over, but the engine can offer to draw, claim 
7812            a draw, or resign. 
7813          */
7814 #if ZIPPY
7815         if (appData.zippyPlay && first.initDone) {
7816             if (result == GameIsDrawn) {
7817                 /* In case draw still needs to be claimed */
7818                 SendToICS(ics_prefix);
7819                 SendToICS("draw\n");
7820             } else if (StrCaseStr(resultDetails, "resign")) {
7821                 SendToICS(ics_prefix);
7822                 SendToICS("resign\n");
7823             }
7824         }
7825 #endif
7826         endingGame = 0; /* [HGM] crash */
7827         return;
7828     }
7829
7830     /* If we're loading the game from a file, stop */
7831     if (whosays == GE_FILE) {
7832       (void) StopLoadGameTimer();
7833       gameFileFP = NULL;
7834     }
7835
7836     /* Cancel draw offers */
7837     first.offeredDraw = second.offeredDraw = 0;
7838
7839     /* If this is an ICS game, only ICS can really say it's done;
7840        if not, anyone can. */
7841     isIcsGame = (gameMode == IcsPlayingWhite || 
7842                  gameMode == IcsPlayingBlack || 
7843                  gameMode == IcsObserving    || 
7844                  gameMode == IcsExamining);
7845
7846     if (!isIcsGame || whosays == GE_ICS) {
7847         /* OK -- not an ICS game, or ICS said it was done */
7848         StopClocks();
7849         if (!isIcsGame && !appData.noChessProgram) 
7850           SetUserThinkingEnables();
7851     
7852         /* [HGM] if a machine claims the game end we verify this claim */
7853         if(gameMode == TwoMachinesPlay && appData.testClaims) {
7854             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7855                 char claimer;
7856                 ChessMove trueResult = (ChessMove) -1;
7857
7858                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
7859                                             first.twoMachinesColor[0] :
7860                                             second.twoMachinesColor[0] ;
7861
7862                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7863                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7864                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7865                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7866                 } else
7867                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7868                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7869                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7870                 } else
7871                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7872                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7873                 }
7874
7875                 // now verify win claims, but not in drop games, as we don't understand those yet
7876                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7877                                                  || gameInfo.variant == VariantGreat) &&
7878                     (result == WhiteWins && claimer == 'w' ||
7879                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
7880                       if (appData.debugMode) {
7881                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
7882                                 result, epStatus[forwardMostMove], forwardMostMove);
7883                       }
7884                       if(result != trueResult) {
7885                               sprintf(buf, "False win claim: '%s'", resultDetails);
7886                               result = claimer == 'w' ? BlackWins : WhiteWins;
7887                               resultDetails = buf;
7888                       }
7889                 } else
7890                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7891                     && (forwardMostMove <= backwardMostMove ||
7892                         epStatus[forwardMostMove-1] > EP_DRAWS ||
7893                         (claimer=='b')==(forwardMostMove&1))
7894                                                                                   ) {
7895                       /* [HGM] verify: draws that were not flagged are false claims */
7896                       sprintf(buf, "False draw claim: '%s'", resultDetails);
7897                       result = claimer == 'w' ? BlackWins : WhiteWins;
7898                       resultDetails = buf;
7899                 }
7900                 /* (Claiming a loss is accepted no questions asked!) */
7901             }
7902             /* [HGM] bare: don't allow bare King to win */
7903             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7904                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
7905                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7906                && result != GameIsDrawn)
7907             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7908                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7909                         int p = (int)boards[forwardMostMove][i][j] - color;
7910                         if(p >= 0 && p <= (int)WhiteKing) k++;
7911                 }
7912                 if (appData.debugMode) {
7913                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7914                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7915                 }
7916                 if(k <= 1) {
7917                         result = GameIsDrawn;
7918                         sprintf(buf, "%s but bare king", resultDetails);
7919                         resultDetails = buf;
7920                 }
7921             }
7922         }
7923
7924
7925         if(serverMoves != NULL && !loadFlag) { char c = '=';
7926             if(result==WhiteWins) c = '+';
7927             if(result==BlackWins) c = '-';
7928             if(resultDetails != NULL)
7929                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7930         }
7931         if (resultDetails != NULL) {
7932             gameInfo.result = result;
7933             gameInfo.resultDetails = StrSave(resultDetails);
7934
7935             /* display last move only if game was not loaded from file */
7936             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7937                 DisplayMove(currentMove - 1);
7938     
7939             if (forwardMostMove != 0) {
7940                 if (gameMode != PlayFromGameFile && gameMode != EditGame
7941                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7942                                                                 ) {
7943                     if (*appData.saveGameFile != NULLCHAR) {
7944                         SaveGameToFile(appData.saveGameFile, TRUE);
7945                     } else if (appData.autoSaveGames) {
7946                         AutoSaveGame();
7947                     }
7948                     if (*appData.savePositionFile != NULLCHAR) {
7949                         SavePositionToFile(appData.savePositionFile);
7950                     }
7951                 }
7952             }
7953
7954             /* Tell program how game ended in case it is learning */
7955             /* [HGM] Moved this to after saving the PGN, just in case */
7956             /* engine died and we got here through time loss. In that */
7957             /* case we will get a fatal error writing the pipe, which */
7958             /* would otherwise lose us the PGN.                       */
7959             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
7960             /* output during GameEnds should never be fatal anymore   */
7961             if (gameMode == MachinePlaysWhite ||
7962                 gameMode == MachinePlaysBlack ||
7963                 gameMode == TwoMachinesPlay ||
7964                 gameMode == IcsPlayingWhite ||
7965                 gameMode == IcsPlayingBlack ||
7966                 gameMode == BeginningOfGame) {
7967                 char buf[MSG_SIZ];
7968                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7969                         resultDetails);
7970                 if (first.pr != NoProc) {
7971                     SendToProgram(buf, &first);
7972                 }
7973                 if (second.pr != NoProc &&
7974                     gameMode == TwoMachinesPlay) {
7975                     SendToProgram(buf, &second);
7976                 }
7977             }
7978         }
7979
7980         if (appData.icsActive) {
7981             if (appData.quietPlay &&
7982                 (gameMode == IcsPlayingWhite ||
7983                  gameMode == IcsPlayingBlack)) {
7984                 SendToICS(ics_prefix);
7985                 SendToICS("set shout 1\n");
7986             }
7987             nextGameMode = IcsIdle;
7988             ics_user_moved = FALSE;
7989             /* clean up premove.  It's ugly when the game has ended and the
7990              * premove highlights are still on the board.
7991              */
7992             if (gotPremove) {
7993               gotPremove = FALSE;
7994               ClearPremoveHighlights();
7995               DrawPosition(FALSE, boards[currentMove]);
7996             }
7997             if (whosays == GE_ICS) {
7998                 switch (result) {
7999                 case WhiteWins:
8000                     if (gameMode == IcsPlayingWhite)
8001                         PlayIcsWinSound();
8002                     else if(gameMode == IcsPlayingBlack)
8003                         PlayIcsLossSound();
8004                     break;
8005                 case BlackWins:
8006                     if (gameMode == IcsPlayingBlack)
8007                         PlayIcsWinSound();
8008                     else if(gameMode == IcsPlayingWhite)
8009                         PlayIcsLossSound();
8010                     break;
8011                 case GameIsDrawn:
8012                     PlayIcsDrawSound();
8013                     break;
8014                 default:
8015                     PlayIcsUnfinishedSound();
8016                 }
8017             }
8018         } else if (gameMode == EditGame ||
8019                    gameMode == PlayFromGameFile || 
8020                    gameMode == AnalyzeMode || 
8021                    gameMode == AnalyzeFile) {
8022             nextGameMode = gameMode;
8023         } else {
8024             nextGameMode = EndOfGame;
8025         }
8026         pausing = FALSE;
8027         ModeHighlight();
8028     } else {
8029         nextGameMode = gameMode;
8030     }
8031
8032     if (appData.noChessProgram) {
8033         gameMode = nextGameMode;
8034         ModeHighlight();
8035         endingGame = 0; /* [HGM] crash */
8036         return;
8037     }
8038
8039     if (first.reuse) {
8040         /* Put first chess program into idle state */
8041         if (first.pr != NoProc &&
8042             (gameMode == MachinePlaysWhite ||
8043              gameMode == MachinePlaysBlack ||
8044              gameMode == TwoMachinesPlay ||
8045              gameMode == IcsPlayingWhite ||
8046              gameMode == IcsPlayingBlack ||
8047              gameMode == BeginningOfGame)) {
8048             SendToProgram("force\n", &first);
8049             if (first.usePing) {
8050               char buf[MSG_SIZ];
8051               sprintf(buf, "ping %d\n", ++first.lastPing);
8052               SendToProgram(buf, &first);
8053             }
8054         }
8055     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8056         /* Kill off first chess program */
8057         if (first.isr != NULL)
8058           RemoveInputSource(first.isr);
8059         first.isr = NULL;
8060     
8061         if (first.pr != NoProc) {
8062             ExitAnalyzeMode();
8063             DoSleep( appData.delayBeforeQuit );
8064             SendToProgram("quit\n", &first);
8065             DoSleep( appData.delayAfterQuit );
8066             DestroyChildProcess(first.pr, first.useSigterm);
8067         }
8068         first.pr = NoProc;
8069     }
8070     if (second.reuse) {
8071         /* Put second chess program into idle state */
8072         if (second.pr != NoProc &&
8073             gameMode == TwoMachinesPlay) {
8074             SendToProgram("force\n", &second);
8075             if (second.usePing) {
8076               char buf[MSG_SIZ];
8077               sprintf(buf, "ping %d\n", ++second.lastPing);
8078               SendToProgram(buf, &second);
8079             }
8080         }
8081     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8082         /* Kill off second chess program */
8083         if (second.isr != NULL)
8084           RemoveInputSource(second.isr);
8085         second.isr = NULL;
8086     
8087         if (second.pr != NoProc) {
8088             DoSleep( appData.delayBeforeQuit );
8089             SendToProgram("quit\n", &second);
8090             DoSleep( appData.delayAfterQuit );
8091             DestroyChildProcess(second.pr, second.useSigterm);
8092         }
8093         second.pr = NoProc;
8094     }
8095
8096     if (matchMode && gameMode == TwoMachinesPlay) {
8097         switch (result) {
8098         case WhiteWins:
8099           if (first.twoMachinesColor[0] == 'w') {
8100             first.matchWins++;
8101           } else {
8102             second.matchWins++;
8103           }
8104           break;
8105         case BlackWins:
8106           if (first.twoMachinesColor[0] == 'b') {
8107             first.matchWins++;
8108           } else {
8109             second.matchWins++;
8110           }
8111           break;
8112         default:
8113           break;
8114         }
8115         if (matchGame < appData.matchGames) {
8116             char *tmp;
8117             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8118                 tmp = first.twoMachinesColor;
8119                 first.twoMachinesColor = second.twoMachinesColor;
8120                 second.twoMachinesColor = tmp;
8121             }
8122             gameMode = nextGameMode;
8123             matchGame++;
8124             if(appData.matchPause>10000 || appData.matchPause<10)
8125                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8126             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8127             endingGame = 0; /* [HGM] crash */
8128             return;
8129         } else {
8130             char buf[MSG_SIZ];
8131             gameMode = nextGameMode;
8132             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8133                     first.tidy, second.tidy,
8134                     first.matchWins, second.matchWins,
8135                     appData.matchGames - (first.matchWins + second.matchWins));
8136             DisplayFatalError(buf, 0, 0);
8137         }
8138     }
8139     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8140         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8141       ExitAnalyzeMode();
8142     gameMode = nextGameMode;
8143     ModeHighlight();
8144     endingGame = 0;  /* [HGM] crash */
8145 }
8146
8147 /* Assumes program was just initialized (initString sent).
8148    Leaves program in force mode. */
8149 void
8150 FeedMovesToProgram(cps, upto) 
8151      ChessProgramState *cps;
8152      int upto;
8153 {
8154     int i;
8155     
8156     if (appData.debugMode)
8157       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8158               startedFromSetupPosition ? "position and " : "",
8159               backwardMostMove, upto, cps->which);
8160     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8161         // [HGM] variantswitch: make engine aware of new variant
8162         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8163                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8164         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8165         SendToProgram(buf, cps);
8166         currentlyInitializedVariant = gameInfo.variant;
8167     }
8168     SendToProgram("force\n", cps);
8169     if (startedFromSetupPosition) {
8170         SendBoard(cps, backwardMostMove);
8171     if (appData.debugMode) {
8172         fprintf(debugFP, "feedMoves\n");
8173     }
8174     }
8175     for (i = backwardMostMove; i < upto; i++) {
8176         SendMoveToProgram(i, cps);
8177     }
8178 }
8179
8180
8181 void
8182 ResurrectChessProgram()
8183 {
8184      /* The chess program may have exited.
8185         If so, restart it and feed it all the moves made so far. */
8186
8187     if (appData.noChessProgram || first.pr != NoProc) return;
8188     
8189     StartChessProgram(&first);
8190     InitChessProgram(&first, FALSE);
8191     FeedMovesToProgram(&first, currentMove);
8192
8193     if (!first.sendTime) {
8194         /* can't tell gnuchess what its clock should read,
8195            so we bow to its notion. */
8196         ResetClocks();
8197         timeRemaining[0][currentMove] = whiteTimeRemaining;
8198         timeRemaining[1][currentMove] = blackTimeRemaining;
8199     }
8200
8201     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8202                 appData.icsEngineAnalyze) && first.analysisSupport) {
8203       SendToProgram("analyze\n", &first);
8204       first.analyzing = TRUE;
8205     }
8206 }
8207
8208 /*
8209  * Button procedures
8210  */
8211 void
8212 Reset(redraw, init)
8213      int redraw, init;
8214 {
8215     int i;
8216
8217     if (appData.debugMode) {
8218         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8219                 redraw, init, gameMode);
8220     }
8221     pausing = pauseExamInvalid = FALSE;
8222     startedFromSetupPosition = blackPlaysFirst = FALSE;
8223     firstMove = TRUE;
8224     whiteFlag = blackFlag = FALSE;
8225     userOfferedDraw = FALSE;
8226     hintRequested = bookRequested = FALSE;
8227     first.maybeThinking = FALSE;
8228     second.maybeThinking = FALSE;
8229     first.bookSuspend = FALSE; // [HGM] book
8230     second.bookSuspend = FALSE;
8231     thinkOutput[0] = NULLCHAR;
8232     lastHint[0] = NULLCHAR;
8233     ClearGameInfo(&gameInfo);
8234     gameInfo.variant = StringToVariant(appData.variant);
8235     ics_user_moved = ics_clock_paused = FALSE;
8236     ics_getting_history = H_FALSE;
8237     ics_gamenum = -1;
8238     white_holding[0] = black_holding[0] = NULLCHAR;
8239     ClearProgramStats();
8240     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8241     
8242     ResetFrontEnd();
8243     ClearHighlights();
8244     flipView = appData.flipView;
8245     ClearPremoveHighlights();
8246     gotPremove = FALSE;
8247     alarmSounded = FALSE;
8248
8249     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8250     if(appData.serverMovesName != NULL) {
8251         /* [HGM] prepare to make moves file for broadcasting */
8252         clock_t t = clock();
8253         if(serverMoves != NULL) fclose(serverMoves);
8254         serverMoves = fopen(appData.serverMovesName, "r");
8255         if(serverMoves != NULL) {
8256             fclose(serverMoves);
8257             /* delay 15 sec before overwriting, so all clients can see end */
8258             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8259         }
8260         serverMoves = fopen(appData.serverMovesName, "w");
8261     }
8262
8263     ExitAnalyzeMode();
8264     gameMode = BeginningOfGame;
8265     ModeHighlight();
8266     if(appData.icsActive) gameInfo.variant = VariantNormal;
8267     InitPosition(redraw);
8268     for (i = 0; i < MAX_MOVES; i++) {
8269         if (commentList[i] != NULL) {
8270             free(commentList[i]);
8271             commentList[i] = NULL;
8272         }
8273     }
8274     ResetClocks();
8275     timeRemaining[0][0] = whiteTimeRemaining;
8276     timeRemaining[1][0] = blackTimeRemaining;
8277     if (first.pr == NULL) {
8278         StartChessProgram(&first);
8279     }
8280     if (init) {
8281             InitChessProgram(&first, startedFromSetupPosition);
8282     }
8283     DisplayTitle("");
8284     DisplayMessage("", "");
8285     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8286     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8287 }
8288
8289 void
8290 AutoPlayGameLoop()
8291 {
8292     for (;;) {
8293         if (!AutoPlayOneMove())
8294           return;
8295         if (matchMode || appData.timeDelay == 0)
8296           continue;
8297         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8298           return;
8299         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8300         break;
8301     }
8302 }
8303
8304
8305 int
8306 AutoPlayOneMove()
8307 {
8308     int fromX, fromY, toX, toY;
8309
8310     if (appData.debugMode) {
8311       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8312     }
8313
8314     if (gameMode != PlayFromGameFile)
8315       return FALSE;
8316
8317     if (currentMove >= forwardMostMove) {
8318       gameMode = EditGame;
8319       ModeHighlight();
8320
8321       /* [AS] Clear current move marker at the end of a game */
8322       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8323
8324       return FALSE;
8325     }
8326     
8327     toX = moveList[currentMove][2] - AAA;
8328     toY = moveList[currentMove][3] - ONE;
8329
8330     if (moveList[currentMove][1] == '@') {
8331         if (appData.highlightLastMove) {
8332             SetHighlights(-1, -1, toX, toY);
8333         }
8334     } else {
8335         fromX = moveList[currentMove][0] - AAA;
8336         fromY = moveList[currentMove][1] - ONE;
8337
8338         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8339
8340         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8341
8342         if (appData.highlightLastMove) {
8343             SetHighlights(fromX, fromY, toX, toY);
8344         }
8345     }
8346     DisplayMove(currentMove);
8347     SendMoveToProgram(currentMove++, &first);
8348     DisplayBothClocks();
8349     DrawPosition(FALSE, boards[currentMove]);
8350     // [HGM] PV info: always display, routine tests if empty
8351     DisplayComment(currentMove - 1, commentList[currentMove]);
8352     return TRUE;
8353 }
8354
8355
8356 int
8357 LoadGameOneMove(readAhead)
8358      ChessMove readAhead;
8359 {
8360     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8361     char promoChar = NULLCHAR;
8362     ChessMove moveType;
8363     char move[MSG_SIZ];
8364     char *p, *q;
8365     
8366     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8367         gameMode != AnalyzeMode && gameMode != Training) {
8368         gameFileFP = NULL;
8369         return FALSE;
8370     }
8371     
8372     yyboardindex = forwardMostMove;
8373     if (readAhead != (ChessMove)0) {
8374       moveType = readAhead;
8375     } else {
8376       if (gameFileFP == NULL)
8377           return FALSE;
8378       moveType = (ChessMove) yylex();
8379     }
8380     
8381     done = FALSE;
8382     switch (moveType) {
8383       case Comment:
8384         if (appData.debugMode) 
8385           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8386         p = yy_text;
8387         if (*p == '{' || *p == '[' || *p == '(') {
8388             p[strlen(p) - 1] = NULLCHAR;
8389             p++;
8390         }
8391
8392         /* append the comment but don't display it */
8393         while (*p == '\n') p++;
8394         AppendComment(currentMove, p);
8395         return TRUE;
8396
8397       case WhiteCapturesEnPassant:
8398       case BlackCapturesEnPassant:
8399       case WhitePromotionChancellor:
8400       case BlackPromotionChancellor:
8401       case WhitePromotionArchbishop:
8402       case BlackPromotionArchbishop:
8403       case WhitePromotionCentaur:
8404       case BlackPromotionCentaur:
8405       case WhitePromotionQueen:
8406       case BlackPromotionQueen:
8407       case WhitePromotionRook:
8408       case BlackPromotionRook:
8409       case WhitePromotionBishop:
8410       case BlackPromotionBishop:
8411       case WhitePromotionKnight:
8412       case BlackPromotionKnight:
8413       case WhitePromotionKing:
8414       case BlackPromotionKing:
8415       case NormalMove:
8416       case WhiteKingSideCastle:
8417       case WhiteQueenSideCastle:
8418       case BlackKingSideCastle:
8419       case BlackQueenSideCastle:
8420       case WhiteKingSideCastleWild:
8421       case WhiteQueenSideCastleWild:
8422       case BlackKingSideCastleWild:
8423       case BlackQueenSideCastleWild:
8424       /* PUSH Fabien */
8425       case WhiteHSideCastleFR:
8426       case WhiteASideCastleFR:
8427       case BlackHSideCastleFR:
8428       case BlackASideCastleFR:
8429       /* POP Fabien */
8430         if (appData.debugMode)
8431           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8432         fromX = currentMoveString[0] - AAA;
8433         fromY = currentMoveString[1] - ONE;
8434         toX = currentMoveString[2] - AAA;
8435         toY = currentMoveString[3] - ONE;
8436         promoChar = currentMoveString[4];
8437         break;
8438
8439       case WhiteDrop:
8440       case BlackDrop:
8441         if (appData.debugMode)
8442           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8443         fromX = moveType == WhiteDrop ?
8444           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8445         (int) CharToPiece(ToLower(currentMoveString[0]));
8446         fromY = DROP_RANK;
8447         toX = currentMoveString[2] - AAA;
8448         toY = currentMoveString[3] - ONE;
8449         break;
8450
8451       case WhiteWins:
8452       case BlackWins:
8453       case GameIsDrawn:
8454       case GameUnfinished:
8455         if (appData.debugMode)
8456           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8457         p = strchr(yy_text, '{');
8458         if (p == NULL) p = strchr(yy_text, '(');
8459         if (p == NULL) {
8460             p = yy_text;
8461             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8462         } else {
8463             q = strchr(p, *p == '{' ? '}' : ')');
8464             if (q != NULL) *q = NULLCHAR;
8465             p++;
8466         }
8467         GameEnds(moveType, p, GE_FILE);
8468         done = TRUE;
8469         if (cmailMsgLoaded) {
8470             ClearHighlights();
8471             flipView = WhiteOnMove(currentMove);
8472             if (moveType == GameUnfinished) flipView = !flipView;
8473             if (appData.debugMode)
8474               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8475         }
8476         break;
8477
8478       case (ChessMove) 0:       /* end of file */
8479         if (appData.debugMode)
8480           fprintf(debugFP, "Parser hit end of file\n");
8481         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8482                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8483           case MT_NONE:
8484           case MT_CHECK:
8485             break;
8486           case MT_CHECKMATE:
8487           case MT_STAINMATE:
8488             if (WhiteOnMove(currentMove)) {
8489                 GameEnds(BlackWins, "Black mates", GE_FILE);
8490             } else {
8491                 GameEnds(WhiteWins, "White mates", GE_FILE);
8492             }
8493             break;
8494           case MT_STALEMATE:
8495             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8496             break;
8497         }
8498         done = TRUE;
8499         break;
8500
8501       case MoveNumberOne:
8502         if (lastLoadGameStart == GNUChessGame) {
8503             /* GNUChessGames have numbers, but they aren't move numbers */
8504             if (appData.debugMode)
8505               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8506                       yy_text, (int) moveType);
8507             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8508         }
8509         /* else fall thru */
8510
8511       case XBoardGame:
8512       case GNUChessGame:
8513       case PGNTag:
8514         /* Reached start of next game in file */
8515         if (appData.debugMode)
8516           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8517         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8518                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8519           case MT_NONE:
8520           case MT_CHECK:
8521             break;
8522           case MT_CHECKMATE:
8523           case MT_STAINMATE:
8524             if (WhiteOnMove(currentMove)) {
8525                 GameEnds(BlackWins, "Black mates", GE_FILE);
8526             } else {
8527                 GameEnds(WhiteWins, "White mates", GE_FILE);
8528             }
8529             break;
8530           case MT_STALEMATE:
8531             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8532             break;
8533         }
8534         done = TRUE;
8535         break;
8536
8537       case PositionDiagram:     /* should not happen; ignore */
8538       case ElapsedTime:         /* ignore */
8539       case NAG:                 /* ignore */
8540         if (appData.debugMode)
8541           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8542                   yy_text, (int) moveType);
8543         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8544
8545       case IllegalMove:
8546         if (appData.testLegality) {
8547             if (appData.debugMode)
8548               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8549             sprintf(move, _("Illegal move: %d.%s%s"),
8550                     (forwardMostMove / 2) + 1,
8551                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8552             DisplayError(move, 0);
8553             done = TRUE;
8554         } else {
8555             if (appData.debugMode)
8556               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8557                       yy_text, currentMoveString);
8558             fromX = currentMoveString[0] - AAA;
8559             fromY = currentMoveString[1] - ONE;
8560             toX = currentMoveString[2] - AAA;
8561             toY = currentMoveString[3] - ONE;
8562             promoChar = currentMoveString[4];
8563         }
8564         break;
8565
8566       case AmbiguousMove:
8567         if (appData.debugMode)
8568           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8569         sprintf(move, _("Ambiguous move: %d.%s%s"),
8570                 (forwardMostMove / 2) + 1,
8571                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8572         DisplayError(move, 0);
8573         done = TRUE;
8574         break;
8575
8576       default:
8577       case ImpossibleMove:
8578         if (appData.debugMode)
8579           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8580         sprintf(move, _("Illegal move: %d.%s%s"),
8581                 (forwardMostMove / 2) + 1,
8582                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8583         DisplayError(move, 0);
8584         done = TRUE;
8585         break;
8586     }
8587
8588     if (done) {
8589         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8590             DrawPosition(FALSE, boards[currentMove]);
8591             DisplayBothClocks();
8592             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8593               DisplayComment(currentMove - 1, commentList[currentMove]);
8594         }
8595         (void) StopLoadGameTimer();
8596         gameFileFP = NULL;
8597         cmailOldMove = forwardMostMove;
8598         return FALSE;
8599     } else {
8600         /* currentMoveString is set as a side-effect of yylex */
8601         strcat(currentMoveString, "\n");
8602         strcpy(moveList[forwardMostMove], currentMoveString);
8603         
8604         thinkOutput[0] = NULLCHAR;
8605         MakeMove(fromX, fromY, toX, toY, promoChar);
8606         currentMove = forwardMostMove;
8607         return TRUE;
8608     }
8609 }
8610
8611 /* Load the nth game from the given file */
8612 int
8613 LoadGameFromFile(filename, n, title, useList)
8614      char *filename;
8615      int n;
8616      char *title;
8617      /*Boolean*/ int useList;
8618 {
8619     FILE *f;
8620     char buf[MSG_SIZ];
8621
8622     if (strcmp(filename, "-") == 0) {
8623         f = stdin;
8624         title = "stdin";
8625     } else {
8626         f = fopen(filename, "rb");
8627         if (f == NULL) {
8628           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8629             DisplayError(buf, errno);
8630             return FALSE;
8631         }
8632     }
8633     if (fseek(f, 0, 0) == -1) {
8634         /* f is not seekable; probably a pipe */
8635         useList = FALSE;
8636     }
8637     if (useList && n == 0) {
8638         int error = GameListBuild(f);
8639         if (error) {
8640             DisplayError(_("Cannot build game list"), error);
8641         } else if (!ListEmpty(&gameList) &&
8642                    ((ListGame *) gameList.tailPred)->number > 1) {
8643             GameListPopUp(f, title);
8644             return TRUE;
8645         }
8646         GameListDestroy();
8647         n = 1;
8648     }
8649     if (n == 0) n = 1;
8650     return LoadGame(f, n, title, FALSE);
8651 }
8652
8653
8654 void
8655 MakeRegisteredMove()
8656 {
8657     int fromX, fromY, toX, toY;
8658     char promoChar;
8659     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8660         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8661           case CMAIL_MOVE:
8662           case CMAIL_DRAW:
8663             if (appData.debugMode)
8664               fprintf(debugFP, "Restoring %s for game %d\n",
8665                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8666     
8667             thinkOutput[0] = NULLCHAR;
8668             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8669             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8670             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8671             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8672             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8673             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8674             MakeMove(fromX, fromY, toX, toY, promoChar);
8675             ShowMove(fromX, fromY, toX, toY);
8676               
8677             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8678                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8679               case MT_NONE:
8680               case MT_CHECK:
8681                 break;
8682                 
8683               case MT_CHECKMATE:
8684               case MT_STAINMATE:
8685                 if (WhiteOnMove(currentMove)) {
8686                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8687                 } else {
8688                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8689                 }
8690                 break;
8691                 
8692               case MT_STALEMATE:
8693                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8694                 break;
8695             }
8696
8697             break;
8698             
8699           case CMAIL_RESIGN:
8700             if (WhiteOnMove(currentMove)) {
8701                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8702             } else {
8703                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8704             }
8705             break;
8706             
8707           case CMAIL_ACCEPT:
8708             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8709             break;
8710               
8711           default:
8712             break;
8713         }
8714     }
8715
8716     return;
8717 }
8718
8719 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8720 int
8721 CmailLoadGame(f, gameNumber, title, useList)
8722      FILE *f;
8723      int gameNumber;
8724      char *title;
8725      int useList;
8726 {
8727     int retVal;
8728
8729     if (gameNumber > nCmailGames) {
8730         DisplayError(_("No more games in this message"), 0);
8731         return FALSE;
8732     }
8733     if (f == lastLoadGameFP) {
8734         int offset = gameNumber - lastLoadGameNumber;
8735         if (offset == 0) {
8736             cmailMsg[0] = NULLCHAR;
8737             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8738                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8739                 nCmailMovesRegistered--;
8740             }
8741             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8742             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8743                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8744             }
8745         } else {
8746             if (! RegisterMove()) return FALSE;
8747         }
8748     }
8749
8750     retVal = LoadGame(f, gameNumber, title, useList);
8751
8752     /* Make move registered during previous look at this game, if any */
8753     MakeRegisteredMove();
8754
8755     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8756         commentList[currentMove]
8757           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8758         DisplayComment(currentMove - 1, commentList[currentMove]);
8759     }
8760
8761     return retVal;
8762 }
8763
8764 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8765 int
8766 ReloadGame(offset)
8767      int offset;
8768 {
8769     int gameNumber = lastLoadGameNumber + offset;
8770     if (lastLoadGameFP == NULL) {
8771         DisplayError(_("No game has been loaded yet"), 0);
8772         return FALSE;
8773     }
8774     if (gameNumber <= 0) {
8775         DisplayError(_("Can't back up any further"), 0);
8776         return FALSE;
8777     }
8778     if (cmailMsgLoaded) {
8779         return CmailLoadGame(lastLoadGameFP, gameNumber,
8780                              lastLoadGameTitle, lastLoadGameUseList);
8781     } else {
8782         return LoadGame(lastLoadGameFP, gameNumber,
8783                         lastLoadGameTitle, lastLoadGameUseList);
8784     }
8785 }
8786
8787
8788
8789 /* Load the nth game from open file f */
8790 int
8791 LoadGame(f, gameNumber, title, useList)
8792      FILE *f;
8793      int gameNumber;
8794      char *title;
8795      int useList;
8796 {
8797     ChessMove cm;
8798     char buf[MSG_SIZ];
8799     int gn = gameNumber;
8800     ListGame *lg = NULL;
8801     int numPGNTags = 0;
8802     int err;
8803     GameMode oldGameMode;
8804     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8805
8806     if (appData.debugMode) 
8807         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8808
8809     if (gameMode == Training )
8810         SetTrainingModeOff();
8811
8812     oldGameMode = gameMode;
8813     if (gameMode != BeginningOfGame) {
8814       Reset(FALSE, TRUE);
8815     }
8816
8817     gameFileFP = f;
8818     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8819         fclose(lastLoadGameFP);
8820     }
8821
8822     if (useList) {
8823         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8824         
8825         if (lg) {
8826             fseek(f, lg->offset, 0);
8827             GameListHighlight(gameNumber);
8828             gn = 1;
8829         }
8830         else {
8831             DisplayError(_("Game number out of range"), 0);
8832             return FALSE;
8833         }
8834     } else {
8835         GameListDestroy();
8836         if (fseek(f, 0, 0) == -1) {
8837             if (f == lastLoadGameFP ?
8838                 gameNumber == lastLoadGameNumber + 1 :
8839                 gameNumber == 1) {
8840                 gn = 1;
8841             } else {
8842                 DisplayError(_("Can't seek on game file"), 0);
8843                 return FALSE;
8844             }
8845         }
8846     }
8847     lastLoadGameFP = f;
8848     lastLoadGameNumber = gameNumber;
8849     strcpy(lastLoadGameTitle, title);
8850     lastLoadGameUseList = useList;
8851
8852     yynewfile(f);
8853
8854     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8855       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8856                 lg->gameInfo.black);
8857             DisplayTitle(buf);
8858     } else if (*title != NULLCHAR) {
8859         if (gameNumber > 1) {
8860             sprintf(buf, "%s %d", title, gameNumber);
8861             DisplayTitle(buf);
8862         } else {
8863             DisplayTitle(title);
8864         }
8865     }
8866
8867     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8868         gameMode = PlayFromGameFile;
8869         ModeHighlight();
8870     }
8871
8872     currentMove = forwardMostMove = backwardMostMove = 0;
8873     CopyBoard(boards[0], initialPosition);
8874     StopClocks();
8875
8876     /*
8877      * Skip the first gn-1 games in the file.
8878      * Also skip over anything that precedes an identifiable 
8879      * start of game marker, to avoid being confused by 
8880      * garbage at the start of the file.  Currently 
8881      * recognized start of game markers are the move number "1",
8882      * the pattern "gnuchess .* game", the pattern
8883      * "^[#;%] [^ ]* game file", and a PGN tag block.  
8884      * A game that starts with one of the latter two patterns
8885      * will also have a move number 1, possibly
8886      * following a position diagram.
8887      * 5-4-02: Let's try being more lenient and allowing a game to
8888      * start with an unnumbered move.  Does that break anything?
8889      */
8890     cm = lastLoadGameStart = (ChessMove) 0;
8891     while (gn > 0) {
8892         yyboardindex = forwardMostMove;
8893         cm = (ChessMove) yylex();
8894         switch (cm) {
8895           case (ChessMove) 0:
8896             if (cmailMsgLoaded) {
8897                 nCmailGames = CMAIL_MAX_GAMES - gn;
8898             } else {
8899                 Reset(TRUE, TRUE);
8900                 DisplayError(_("Game not found in file"), 0);
8901             }
8902             return FALSE;
8903
8904           case GNUChessGame:
8905           case XBoardGame:
8906             gn--;
8907             lastLoadGameStart = cm;
8908             break;
8909             
8910           case MoveNumberOne:
8911             switch (lastLoadGameStart) {
8912               case GNUChessGame:
8913               case XBoardGame:
8914               case PGNTag:
8915                 break;
8916               case MoveNumberOne:
8917               case (ChessMove) 0:
8918                 gn--;           /* count this game */
8919                 lastLoadGameStart = cm;
8920                 break;
8921               default:
8922                 /* impossible */
8923                 break;
8924             }
8925             break;
8926
8927           case PGNTag:
8928             switch (lastLoadGameStart) {
8929               case GNUChessGame:
8930               case PGNTag:
8931               case MoveNumberOne:
8932               case (ChessMove) 0:
8933                 gn--;           /* count this game */
8934                 lastLoadGameStart = cm;
8935                 break;
8936               case XBoardGame:
8937                 lastLoadGameStart = cm; /* game counted already */
8938                 break;
8939               default:
8940                 /* impossible */
8941                 break;
8942             }
8943             if (gn > 0) {
8944                 do {
8945                     yyboardindex = forwardMostMove;
8946                     cm = (ChessMove) yylex();
8947                 } while (cm == PGNTag || cm == Comment);
8948             }
8949             break;
8950
8951           case WhiteWins:
8952           case BlackWins:
8953           case GameIsDrawn:
8954             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8955                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
8956                     != CMAIL_OLD_RESULT) {
8957                     nCmailResults ++ ;
8958                     cmailResult[  CMAIL_MAX_GAMES
8959                                 - gn - 1] = CMAIL_OLD_RESULT;
8960                 }
8961             }
8962             break;
8963
8964           case NormalMove:
8965             /* Only a NormalMove can be at the start of a game
8966              * without a position diagram. */
8967             if (lastLoadGameStart == (ChessMove) 0) {
8968               gn--;
8969               lastLoadGameStart = MoveNumberOne;
8970             }
8971             break;
8972
8973           default:
8974             break;
8975         }
8976     }
8977     
8978     if (appData.debugMode)
8979       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8980
8981     if (cm == XBoardGame) {
8982         /* Skip any header junk before position diagram and/or move 1 */
8983         for (;;) {
8984             yyboardindex = forwardMostMove;
8985             cm = (ChessMove) yylex();
8986
8987             if (cm == (ChessMove) 0 ||
8988                 cm == GNUChessGame || cm == XBoardGame) {
8989                 /* Empty game; pretend end-of-file and handle later */
8990                 cm = (ChessMove) 0;
8991                 break;
8992             }
8993
8994             if (cm == MoveNumberOne || cm == PositionDiagram ||
8995                 cm == PGNTag || cm == Comment)
8996               break;
8997         }
8998     } else if (cm == GNUChessGame) {
8999         if (gameInfo.event != NULL) {
9000             free(gameInfo.event);
9001         }
9002         gameInfo.event = StrSave(yy_text);
9003     }   
9004
9005     startedFromSetupPosition = FALSE;
9006     while (cm == PGNTag) {
9007         if (appData.debugMode) 
9008           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9009         err = ParsePGNTag(yy_text, &gameInfo);
9010         if (!err) numPGNTags++;
9011
9012         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9013         if(gameInfo.variant != oldVariant) {
9014             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9015             InitPosition(TRUE);
9016             oldVariant = gameInfo.variant;
9017             if (appData.debugMode) 
9018               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9019         }
9020
9021
9022         if (gameInfo.fen != NULL) {
9023           Board initial_position;
9024           startedFromSetupPosition = TRUE;
9025           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9026             Reset(TRUE, TRUE);
9027             DisplayError(_("Bad FEN position in file"), 0);
9028             return FALSE;
9029           }
9030           CopyBoard(boards[0], initial_position);
9031           if (blackPlaysFirst) {
9032             currentMove = forwardMostMove = backwardMostMove = 1;
9033             CopyBoard(boards[1], initial_position);
9034             strcpy(moveList[0], "");
9035             strcpy(parseList[0], "");
9036             timeRemaining[0][1] = whiteTimeRemaining;
9037             timeRemaining[1][1] = blackTimeRemaining;
9038             if (commentList[0] != NULL) {
9039               commentList[1] = commentList[0];
9040               commentList[0] = NULL;
9041             }
9042           } else {
9043             currentMove = forwardMostMove = backwardMostMove = 0;
9044           }
9045           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9046           {   int i;
9047               initialRulePlies = FENrulePlies;
9048               epStatus[forwardMostMove] = FENepStatus;
9049               for( i=0; i< nrCastlingRights; i++ )
9050                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9051           }
9052           yyboardindex = forwardMostMove;
9053           free(gameInfo.fen);
9054           gameInfo.fen = NULL;
9055         }
9056
9057         yyboardindex = forwardMostMove;
9058         cm = (ChessMove) yylex();
9059
9060         /* Handle comments interspersed among the tags */
9061         while (cm == Comment) {
9062             char *p;
9063             if (appData.debugMode) 
9064               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9065             p = yy_text;
9066             if (*p == '{' || *p == '[' || *p == '(') {
9067                 p[strlen(p) - 1] = NULLCHAR;
9068                 p++;
9069             }
9070             while (*p == '\n') p++;
9071             AppendComment(currentMove, p);
9072             yyboardindex = forwardMostMove;
9073             cm = (ChessMove) yylex();
9074         }
9075     }
9076
9077     /* don't rely on existence of Event tag since if game was
9078      * pasted from clipboard the Event tag may not exist
9079      */
9080     if (numPGNTags > 0){
9081         char *tags;
9082         if (gameInfo.variant == VariantNormal) {
9083           gameInfo.variant = StringToVariant(gameInfo.event);
9084         }
9085         if (!matchMode) {
9086           if( appData.autoDisplayTags ) {
9087             tags = PGNTags(&gameInfo);
9088             TagsPopUp(tags, CmailMsg());
9089             free(tags);
9090           }
9091         }
9092     } else {
9093         /* Make something up, but don't display it now */
9094         SetGameInfo();
9095         TagsPopDown();
9096     }
9097
9098     if (cm == PositionDiagram) {
9099         int i, j;
9100         char *p;
9101         Board initial_position;
9102
9103         if (appData.debugMode)
9104           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9105
9106         if (!startedFromSetupPosition) {
9107             p = yy_text;
9108             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9109               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9110                 switch (*p) {
9111                   case '[':
9112                   case '-':
9113                   case ' ':
9114                   case '\t':
9115                   case '\n':
9116                   case '\r':
9117                     break;
9118                   default:
9119                     initial_position[i][j++] = CharToPiece(*p);
9120                     break;
9121                 }
9122             while (*p == ' ' || *p == '\t' ||
9123                    *p == '\n' || *p == '\r') p++;
9124         
9125             if (strncmp(p, "black", strlen("black"))==0)
9126               blackPlaysFirst = TRUE;
9127             else
9128               blackPlaysFirst = FALSE;
9129             startedFromSetupPosition = TRUE;
9130         
9131             CopyBoard(boards[0], initial_position);
9132             if (blackPlaysFirst) {
9133                 currentMove = forwardMostMove = backwardMostMove = 1;
9134                 CopyBoard(boards[1], initial_position);
9135                 strcpy(moveList[0], "");
9136                 strcpy(parseList[0], "");
9137                 timeRemaining[0][1] = whiteTimeRemaining;
9138                 timeRemaining[1][1] = blackTimeRemaining;
9139                 if (commentList[0] != NULL) {
9140                     commentList[1] = commentList[0];
9141                     commentList[0] = NULL;
9142                 }
9143             } else {
9144                 currentMove = forwardMostMove = backwardMostMove = 0;
9145             }
9146         }
9147         yyboardindex = forwardMostMove;
9148         cm = (ChessMove) yylex();
9149     }
9150
9151     if (first.pr == NoProc) {
9152         StartChessProgram(&first);
9153     }
9154     InitChessProgram(&first, FALSE);
9155     SendToProgram("force\n", &first);
9156     if (startedFromSetupPosition) {
9157         SendBoard(&first, forwardMostMove);
9158     if (appData.debugMode) {
9159         fprintf(debugFP, "Load Game\n");
9160     }
9161         DisplayBothClocks();
9162     }      
9163
9164     /* [HGM] server: flag to write setup moves in broadcast file as one */
9165     loadFlag = appData.suppressLoadMoves;
9166
9167     while (cm == Comment) {
9168         char *p;
9169         if (appData.debugMode) 
9170           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9171         p = yy_text;
9172         if (*p == '{' || *p == '[' || *p == '(') {
9173             p[strlen(p) - 1] = NULLCHAR;
9174             p++;
9175         }
9176         while (*p == '\n') p++;
9177         AppendComment(currentMove, p);
9178         yyboardindex = forwardMostMove;
9179         cm = (ChessMove) yylex();
9180     }
9181
9182     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9183         cm == WhiteWins || cm == BlackWins ||
9184         cm == GameIsDrawn || cm == GameUnfinished) {
9185         DisplayMessage("", _("No moves in game"));
9186         if (cmailMsgLoaded) {
9187             if (appData.debugMode)
9188               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9189             ClearHighlights();
9190             flipView = FALSE;
9191         }
9192         DrawPosition(FALSE, boards[currentMove]);
9193         DisplayBothClocks();
9194         gameMode = EditGame;
9195         ModeHighlight();
9196         gameFileFP = NULL;
9197         cmailOldMove = 0;
9198         return TRUE;
9199     }
9200
9201     // [HGM] PV info: routine tests if comment empty
9202     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9203         DisplayComment(currentMove - 1, commentList[currentMove]);
9204     }
9205     if (!matchMode && appData.timeDelay != 0) 
9206       DrawPosition(FALSE, boards[currentMove]);
9207
9208     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9209       programStats.ok_to_send = 1;
9210     }
9211
9212     /* if the first token after the PGN tags is a move
9213      * and not move number 1, retrieve it from the parser 
9214      */
9215     if (cm != MoveNumberOne)
9216         LoadGameOneMove(cm);
9217
9218     /* load the remaining moves from the file */
9219     while (LoadGameOneMove((ChessMove)0)) {
9220       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9221       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9222     }
9223
9224     /* rewind to the start of the game */
9225     currentMove = backwardMostMove;
9226
9227     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9228
9229     if (oldGameMode == AnalyzeFile ||
9230         oldGameMode == AnalyzeMode) {
9231       AnalyzeFileEvent();
9232     }
9233
9234     if (matchMode || appData.timeDelay == 0) {
9235       ToEndEvent();
9236       gameMode = EditGame;
9237       ModeHighlight();
9238     } else if (appData.timeDelay > 0) {
9239       AutoPlayGameLoop();
9240     }
9241
9242     if (appData.debugMode) 
9243         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9244
9245     loadFlag = 0; /* [HGM] true game starts */
9246     return TRUE;
9247 }
9248
9249 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9250 int
9251 ReloadPosition(offset)
9252      int offset;
9253 {
9254     int positionNumber = lastLoadPositionNumber + offset;
9255     if (lastLoadPositionFP == NULL) {
9256         DisplayError(_("No position has been loaded yet"), 0);
9257         return FALSE;
9258     }
9259     if (positionNumber <= 0) {
9260         DisplayError(_("Can't back up any further"), 0);
9261         return FALSE;
9262     }
9263     return LoadPosition(lastLoadPositionFP, positionNumber,
9264                         lastLoadPositionTitle);
9265 }
9266
9267 /* Load the nth position from the given file */
9268 int
9269 LoadPositionFromFile(filename, n, title)
9270      char *filename;
9271      int n;
9272      char *title;
9273 {
9274     FILE *f;
9275     char buf[MSG_SIZ];
9276
9277     if (strcmp(filename, "-") == 0) {
9278         return LoadPosition(stdin, n, "stdin");
9279     } else {
9280         f = fopen(filename, "rb");
9281         if (f == NULL) {
9282             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9283             DisplayError(buf, errno);
9284             return FALSE;
9285         } else {
9286             return LoadPosition(f, n, title);
9287         }
9288     }
9289 }
9290
9291 /* Load the nth position from the given open file, and close it */
9292 int
9293 LoadPosition(f, positionNumber, title)
9294      FILE *f;
9295      int positionNumber;
9296      char *title;
9297 {
9298     char *p, line[MSG_SIZ];
9299     Board initial_position;
9300     int i, j, fenMode, pn;
9301     
9302     if (gameMode == Training )
9303         SetTrainingModeOff();
9304
9305     if (gameMode != BeginningOfGame) {
9306         Reset(FALSE, TRUE);
9307     }
9308     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9309         fclose(lastLoadPositionFP);
9310     }
9311     if (positionNumber == 0) positionNumber = 1;
9312     lastLoadPositionFP = f;
9313     lastLoadPositionNumber = positionNumber;
9314     strcpy(lastLoadPositionTitle, title);
9315     if (first.pr == NoProc) {
9316       StartChessProgram(&first);
9317       InitChessProgram(&first, FALSE);
9318     }    
9319     pn = positionNumber;
9320     if (positionNumber < 0) {
9321         /* Negative position number means to seek to that byte offset */
9322         if (fseek(f, -positionNumber, 0) == -1) {
9323             DisplayError(_("Can't seek on position file"), 0);
9324             return FALSE;
9325         };
9326         pn = 1;
9327     } else {
9328         if (fseek(f, 0, 0) == -1) {
9329             if (f == lastLoadPositionFP ?
9330                 positionNumber == lastLoadPositionNumber + 1 :
9331                 positionNumber == 1) {
9332                 pn = 1;
9333             } else {
9334                 DisplayError(_("Can't seek on position file"), 0);
9335                 return FALSE;
9336             }
9337         }
9338     }
9339     /* See if this file is FEN or old-style xboard */
9340     if (fgets(line, MSG_SIZ, f) == NULL) {
9341         DisplayError(_("Position not found in file"), 0);
9342         return FALSE;
9343     }
9344 #if 0
9345     switch (line[0]) {
9346       case '#':  case 'x':
9347       default:
9348         fenMode = FALSE;
9349         break;
9350       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':
9351       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':
9352       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':
9353       case '7':  case '8':  case '9':
9354       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':
9355       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':
9356       case 'C':  case 'W':             case 'c':  case 'w': 
9357         fenMode = TRUE;
9358         break;
9359     }
9360 #else
9361     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9362     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9363 #endif
9364
9365     if (pn >= 2) {
9366         if (fenMode || line[0] == '#') pn--;
9367         while (pn > 0) {
9368             /* skip positions before number pn */
9369             if (fgets(line, MSG_SIZ, f) == NULL) {
9370                 Reset(TRUE, TRUE);
9371                 DisplayError(_("Position not found in file"), 0);
9372                 return FALSE;
9373             }
9374             if (fenMode || line[0] == '#') pn--;
9375         }
9376     }
9377
9378     if (fenMode) {
9379         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9380             DisplayError(_("Bad FEN position in file"), 0);
9381             return FALSE;
9382         }
9383     } else {
9384         (void) fgets(line, MSG_SIZ, f);
9385         (void) fgets(line, MSG_SIZ, f);
9386     
9387         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9388             (void) fgets(line, MSG_SIZ, f);
9389             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9390                 if (*p == ' ')
9391                   continue;
9392                 initial_position[i][j++] = CharToPiece(*p);
9393             }
9394         }
9395     
9396         blackPlaysFirst = FALSE;
9397         if (!feof(f)) {
9398             (void) fgets(line, MSG_SIZ, f);
9399             if (strncmp(line, "black", strlen("black"))==0)
9400               blackPlaysFirst = TRUE;
9401         }
9402     }
9403     startedFromSetupPosition = TRUE;
9404     
9405     SendToProgram("force\n", &first);
9406     CopyBoard(boards[0], initial_position);
9407     if (blackPlaysFirst) {
9408         currentMove = forwardMostMove = backwardMostMove = 1;
9409         strcpy(moveList[0], "");
9410         strcpy(parseList[0], "");
9411         CopyBoard(boards[1], initial_position);
9412         DisplayMessage("", _("Black to play"));
9413     } else {
9414         currentMove = forwardMostMove = backwardMostMove = 0;
9415         DisplayMessage("", _("White to play"));
9416     }
9417           /* [HGM] copy FEN attributes as well */
9418           {   int i;
9419               initialRulePlies = FENrulePlies;
9420               epStatus[forwardMostMove] = FENepStatus;
9421               for( i=0; i< nrCastlingRights; i++ )
9422                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9423           }
9424     SendBoard(&first, forwardMostMove);
9425     if (appData.debugMode) {
9426 int i, j;
9427   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9428   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9429         fprintf(debugFP, "Load Position\n");
9430     }
9431
9432     if (positionNumber > 1) {
9433         sprintf(line, "%s %d", title, positionNumber);
9434         DisplayTitle(line);
9435     } else {
9436         DisplayTitle(title);
9437     }
9438     gameMode = EditGame;
9439     ModeHighlight();
9440     ResetClocks();
9441     timeRemaining[0][1] = whiteTimeRemaining;
9442     timeRemaining[1][1] = blackTimeRemaining;
9443     DrawPosition(FALSE, boards[currentMove]);
9444    
9445     return TRUE;
9446 }
9447
9448
9449 void
9450 CopyPlayerNameIntoFileName(dest, src)
9451      char **dest, *src;
9452 {
9453     while (*src != NULLCHAR && *src != ',') {
9454         if (*src == ' ') {
9455             *(*dest)++ = '_';
9456             src++;
9457         } else {
9458             *(*dest)++ = *src++;
9459         }
9460     }
9461 }
9462
9463 char *DefaultFileName(ext)
9464      char *ext;
9465 {
9466     static char def[MSG_SIZ];
9467     char *p;
9468
9469     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9470         p = def;
9471         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9472         *p++ = '-';
9473         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9474         *p++ = '.';
9475         strcpy(p, ext);
9476     } else {
9477         def[0] = NULLCHAR;
9478     }
9479     return def;
9480 }
9481
9482 /* Save the current game to the given file */
9483 int
9484 SaveGameToFile(filename, append)
9485      char *filename;
9486      int append;
9487 {
9488     FILE *f;
9489     char buf[MSG_SIZ];
9490
9491     if (strcmp(filename, "-") == 0) {
9492         return SaveGame(stdout, 0, NULL);
9493     } else {
9494         f = fopen(filename, append ? "a" : "w");
9495         if (f == NULL) {
9496             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9497             DisplayError(buf, errno);
9498             return FALSE;
9499         } else {
9500             return SaveGame(f, 0, NULL);
9501         }
9502     }
9503 }
9504
9505 char *
9506 SavePart(str)
9507      char *str;
9508 {
9509     static char buf[MSG_SIZ];
9510     char *p;
9511     
9512     p = strchr(str, ' ');
9513     if (p == NULL) return str;
9514     strncpy(buf, str, p - str);
9515     buf[p - str] = NULLCHAR;
9516     return buf;
9517 }
9518
9519 #define PGN_MAX_LINE 75
9520
9521 #define PGN_SIDE_WHITE  0
9522 #define PGN_SIDE_BLACK  1
9523
9524 /* [AS] */
9525 static int FindFirstMoveOutOfBook( int side )
9526 {
9527     int result = -1;
9528
9529     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9530         int index = backwardMostMove;
9531         int has_book_hit = 0;
9532
9533         if( (index % 2) != side ) {
9534             index++;
9535         }
9536
9537         while( index < forwardMostMove ) {
9538             /* Check to see if engine is in book */
9539             int depth = pvInfoList[index].depth;
9540             int score = pvInfoList[index].score;
9541             int in_book = 0;
9542
9543             if( depth <= 2 ) {
9544                 in_book = 1;
9545             }
9546             else if( score == 0 && depth == 63 ) {
9547                 in_book = 1; /* Zappa */
9548             }
9549             else if( score == 2 && depth == 99 ) {
9550                 in_book = 1; /* Abrok */
9551             }
9552
9553             has_book_hit += in_book;
9554
9555             if( ! in_book ) {
9556                 result = index;
9557
9558                 break;
9559             }
9560
9561             index += 2;
9562         }
9563     }
9564
9565     return result;
9566 }
9567
9568 /* [AS] */
9569 void GetOutOfBookInfo( char * buf )
9570 {
9571     int oob[2];
9572     int i;
9573     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9574
9575     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9576     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9577
9578     *buf = '\0';
9579
9580     if( oob[0] >= 0 || oob[1] >= 0 ) {
9581         for( i=0; i<2; i++ ) {
9582             int idx = oob[i];
9583
9584             if( idx >= 0 ) {
9585                 if( i > 0 && oob[0] >= 0 ) {
9586                     strcat( buf, "   " );
9587                 }
9588
9589                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9590                 sprintf( buf+strlen(buf), "%s%.2f", 
9591                     pvInfoList[idx].score >= 0 ? "+" : "",
9592                     pvInfoList[idx].score / 100.0 );
9593             }
9594         }
9595     }
9596 }
9597
9598 /* Save game in PGN style and close the file */
9599 int
9600 SaveGamePGN(f)
9601      FILE *f;
9602 {
9603     int i, offset, linelen, newblock;
9604     time_t tm;
9605 //    char *movetext;
9606     char numtext[32];
9607     int movelen, numlen, blank;
9608     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9609
9610     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9611     
9612     tm = time((time_t *) NULL);
9613     
9614     PrintPGNTags(f, &gameInfo);
9615     
9616     if (backwardMostMove > 0 || startedFromSetupPosition) {
9617         char *fen = PositionToFEN(backwardMostMove, NULL);
9618         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9619         fprintf(f, "\n{--------------\n");
9620         PrintPosition(f, backwardMostMove);
9621         fprintf(f, "--------------}\n");
9622         free(fen);
9623     }
9624     else {
9625         /* [AS] Out of book annotation */
9626         if( appData.saveOutOfBookInfo ) {
9627             char buf[64];
9628
9629             GetOutOfBookInfo( buf );
9630
9631             if( buf[0] != '\0' ) {
9632                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
9633             }
9634         }
9635
9636         fprintf(f, "\n");
9637     }
9638
9639     i = backwardMostMove;
9640     linelen = 0;
9641     newblock = TRUE;
9642
9643     while (i < forwardMostMove) {
9644         /* Print comments preceding this move */
9645         if (commentList[i] != NULL) {
9646             if (linelen > 0) fprintf(f, "\n");
9647             fprintf(f, "{\n%s}\n", commentList[i]);
9648             linelen = 0;
9649             newblock = TRUE;
9650         }
9651
9652         /* Format move number */
9653         if ((i % 2) == 0) {
9654             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9655         } else {
9656             if (newblock) {
9657                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9658             } else {
9659                 numtext[0] = NULLCHAR;
9660             }
9661         }
9662         numlen = strlen(numtext);
9663         newblock = FALSE;
9664
9665         /* Print move number */
9666         blank = linelen > 0 && numlen > 0;
9667         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9668             fprintf(f, "\n");
9669             linelen = 0;
9670             blank = 0;
9671         }
9672         if (blank) {
9673             fprintf(f, " ");
9674             linelen++;
9675         }
9676         fprintf(f, numtext);
9677         linelen += numlen;
9678
9679         /* Get move */
9680         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9681         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9682 #if 0
9683         // SavePart already does this!
9684         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9685                 int p = movelen - 1;
9686                 if(move_buffer[p] == ' ') p--;
9687                 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
9688                     while(p && move_buffer[--p] != '(');
9689                     if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
9690                 }
9691         }
9692 #endif
9693         /* Print move */
9694         blank = linelen > 0 && movelen > 0;
9695         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9696             fprintf(f, "\n");
9697             linelen = 0;
9698             blank = 0;
9699         }
9700         if (blank) {
9701             fprintf(f, " ");
9702             linelen++;
9703         }
9704         fprintf(f, move_buffer);
9705         linelen += movelen;
9706
9707         /* [AS] Add PV info if present */
9708         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9709             /* [HGM] add time */
9710             char buf[MSG_SIZ]; int seconds = 0;
9711
9712 #if 1
9713             if(i >= backwardMostMove) {
9714                 if(WhiteOnMove(i))
9715                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9716                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9717                 else
9718                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9719                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9720             }
9721             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9722 #else
9723             seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
9724 #endif
9725
9726             if( seconds <= 0) buf[0] = 0; else
9727             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9728                 seconds = (seconds + 4)/10; // round to full seconds
9729                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9730                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9731             }
9732
9733             sprintf( move_buffer, "{%s%.2f/%d%s}", 
9734                 pvInfoList[i].score >= 0 ? "+" : "",
9735                 pvInfoList[i].score / 100.0,
9736                 pvInfoList[i].depth,
9737                 buf );
9738
9739             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9740
9741             /* Print score/depth */
9742             blank = linelen > 0 && movelen > 0;
9743             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9744                 fprintf(f, "\n");
9745                 linelen = 0;
9746                 blank = 0;
9747             }
9748             if (blank) {
9749                 fprintf(f, " ");
9750                 linelen++;
9751             }
9752             fprintf(f, move_buffer);
9753             linelen += movelen;
9754         }
9755
9756         i++;
9757     }
9758     
9759     /* Start a new line */
9760     if (linelen > 0) fprintf(f, "\n");
9761
9762     /* Print comments after last move */
9763     if (commentList[i] != NULL) {
9764         fprintf(f, "{\n%s}\n", commentList[i]);
9765     }
9766
9767     /* Print result */
9768     if (gameInfo.resultDetails != NULL &&
9769         gameInfo.resultDetails[0] != NULLCHAR) {
9770         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9771                 PGNResult(gameInfo.result));
9772     } else {
9773         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9774     }
9775
9776     fclose(f);
9777     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9778     return TRUE;
9779 }
9780
9781 /* Save game in old style and close the file */
9782 int
9783 SaveGameOldStyle(f)
9784      FILE *f;
9785 {
9786     int i, offset;
9787     time_t tm;
9788     
9789     tm = time((time_t *) NULL);
9790     
9791     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9792     PrintOpponents(f);
9793     
9794     if (backwardMostMove > 0 || startedFromSetupPosition) {
9795         fprintf(f, "\n[--------------\n");
9796         PrintPosition(f, backwardMostMove);
9797         fprintf(f, "--------------]\n");
9798     } else {
9799         fprintf(f, "\n");
9800     }
9801
9802     i = backwardMostMove;
9803     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9804
9805     while (i < forwardMostMove) {
9806         if (commentList[i] != NULL) {
9807             fprintf(f, "[%s]\n", commentList[i]);
9808         }
9809
9810         if ((i % 2) == 1) {
9811             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
9812             i++;
9813         } else {
9814             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
9815             i++;
9816             if (commentList[i] != NULL) {
9817                 fprintf(f, "\n");
9818                 continue;
9819             }
9820             if (i >= forwardMostMove) {
9821                 fprintf(f, "\n");
9822                 break;
9823             }
9824             fprintf(f, "%s\n", parseList[i]);
9825             i++;
9826         }
9827     }
9828     
9829     if (commentList[i] != NULL) {
9830         fprintf(f, "[%s]\n", commentList[i]);
9831     }
9832
9833     /* This isn't really the old style, but it's close enough */
9834     if (gameInfo.resultDetails != NULL &&
9835         gameInfo.resultDetails[0] != NULLCHAR) {
9836         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9837                 gameInfo.resultDetails);
9838     } else {
9839         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9840     }
9841
9842     fclose(f);
9843     return TRUE;
9844 }
9845
9846 /* Save the current game to open file f and close the file */
9847 int
9848 SaveGame(f, dummy, dummy2)
9849      FILE *f;
9850      int dummy;
9851      char *dummy2;
9852 {
9853     if (gameMode == EditPosition) EditPositionDone();
9854     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9855     if (appData.oldSaveStyle)
9856       return SaveGameOldStyle(f);
9857     else
9858       return SaveGamePGN(f);
9859 }
9860
9861 /* Save the current position to the given file */
9862 int
9863 SavePositionToFile(filename)
9864      char *filename;
9865 {
9866     FILE *f;
9867     char buf[MSG_SIZ];
9868
9869     if (strcmp(filename, "-") == 0) {
9870         return SavePosition(stdout, 0, NULL);
9871     } else {
9872         f = fopen(filename, "a");
9873         if (f == NULL) {
9874             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9875             DisplayError(buf, errno);
9876             return FALSE;
9877         } else {
9878             SavePosition(f, 0, NULL);
9879             return TRUE;
9880         }
9881     }
9882 }
9883
9884 /* Save the current position to the given open file and close the file */
9885 int
9886 SavePosition(f, dummy, dummy2)
9887      FILE *f;
9888      int dummy;
9889      char *dummy2;
9890 {
9891     time_t tm;
9892     char *fen;
9893     
9894     if (appData.oldSaveStyle) {
9895         tm = time((time_t *) NULL);
9896     
9897         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9898         PrintOpponents(f);
9899         fprintf(f, "[--------------\n");
9900         PrintPosition(f, currentMove);
9901         fprintf(f, "--------------]\n");
9902     } else {
9903         fen = PositionToFEN(currentMove, NULL);
9904         fprintf(f, "%s\n", fen);
9905         free(fen);
9906     }
9907     fclose(f);
9908     return TRUE;
9909 }
9910
9911 void
9912 ReloadCmailMsgEvent(unregister)
9913      int unregister;
9914 {
9915 #if !WIN32
9916     static char *inFilename = NULL;
9917     static char *outFilename;
9918     int i;
9919     struct stat inbuf, outbuf;
9920     int status;
9921     
9922     /* Any registered moves are unregistered if unregister is set, */
9923     /* i.e. invoked by the signal handler */
9924     if (unregister) {
9925         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9926             cmailMoveRegistered[i] = FALSE;
9927             if (cmailCommentList[i] != NULL) {
9928                 free(cmailCommentList[i]);
9929                 cmailCommentList[i] = NULL;
9930             }
9931         }
9932         nCmailMovesRegistered = 0;
9933     }
9934
9935     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9936         cmailResult[i] = CMAIL_NOT_RESULT;
9937     }
9938     nCmailResults = 0;
9939
9940     if (inFilename == NULL) {
9941         /* Because the filenames are static they only get malloced once  */
9942         /* and they never get freed                                      */
9943         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9944         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9945
9946         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9947         sprintf(outFilename, "%s.out", appData.cmailGameName);
9948     }
9949     
9950     status = stat(outFilename, &outbuf);
9951     if (status < 0) {
9952         cmailMailedMove = FALSE;
9953     } else {
9954         status = stat(inFilename, &inbuf);
9955         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9956     }
9957     
9958     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9959        counts the games, notes how each one terminated, etc.
9960        
9961        It would be nice to remove this kludge and instead gather all
9962        the information while building the game list.  (And to keep it
9963        in the game list nodes instead of having a bunch of fixed-size
9964        parallel arrays.)  Note this will require getting each game's
9965        termination from the PGN tags, as the game list builder does
9966        not process the game moves.  --mann
9967        */
9968     cmailMsgLoaded = TRUE;
9969     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9970     
9971     /* Load first game in the file or popup game menu */
9972     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9973
9974 #endif /* !WIN32 */
9975     return;
9976 }
9977
9978 int
9979 RegisterMove()
9980 {
9981     FILE *f;
9982     char string[MSG_SIZ];
9983
9984     if (   cmailMailedMove
9985         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9986         return TRUE;            /* Allow free viewing  */
9987     }
9988
9989     /* Unregister move to ensure that we don't leave RegisterMove        */
9990     /* with the move registered when the conditions for registering no   */
9991     /* longer hold                                                       */
9992     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9993         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9994         nCmailMovesRegistered --;
9995
9996         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
9997           {
9998               free(cmailCommentList[lastLoadGameNumber - 1]);
9999               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10000           }
10001     }
10002
10003     if (cmailOldMove == -1) {
10004         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10005         return FALSE;
10006     }
10007
10008     if (currentMove > cmailOldMove + 1) {
10009         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10010         return FALSE;
10011     }
10012
10013     if (currentMove < cmailOldMove) {
10014         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10015         return FALSE;
10016     }
10017
10018     if (forwardMostMove > currentMove) {
10019         /* Silently truncate extra moves */
10020         TruncateGame();
10021     }
10022
10023     if (   (currentMove == cmailOldMove + 1)
10024         || (   (currentMove == cmailOldMove)
10025             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10026                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10027         if (gameInfo.result != GameUnfinished) {
10028             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10029         }
10030
10031         if (commentList[currentMove] != NULL) {
10032             cmailCommentList[lastLoadGameNumber - 1]
10033               = StrSave(commentList[currentMove]);
10034         }
10035         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10036
10037         if (appData.debugMode)
10038           fprintf(debugFP, "Saving %s for game %d\n",
10039                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10040
10041         sprintf(string,
10042                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10043         
10044         f = fopen(string, "w");
10045         if (appData.oldSaveStyle) {
10046             SaveGameOldStyle(f); /* also closes the file */
10047             
10048             sprintf(string, "%s.pos.out", appData.cmailGameName);
10049             f = fopen(string, "w");
10050             SavePosition(f, 0, NULL); /* also closes the file */
10051         } else {
10052             fprintf(f, "{--------------\n");
10053             PrintPosition(f, currentMove);
10054             fprintf(f, "--------------}\n\n");
10055             
10056             SaveGame(f, 0, NULL); /* also closes the file*/
10057         }
10058         
10059         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10060         nCmailMovesRegistered ++;
10061     } else if (nCmailGames == 1) {
10062         DisplayError(_("You have not made a move yet"), 0);
10063         return FALSE;
10064     }
10065
10066     return TRUE;
10067 }
10068
10069 void
10070 MailMoveEvent()
10071 {
10072 #if !WIN32
10073     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10074     FILE *commandOutput;
10075     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10076     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10077     int nBuffers;
10078     int i;
10079     int archived;
10080     char *arcDir;
10081
10082     if (! cmailMsgLoaded) {
10083         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10084         return;
10085     }
10086
10087     if (nCmailGames == nCmailResults) {
10088         DisplayError(_("No unfinished games"), 0);
10089         return;
10090     }
10091
10092 #if CMAIL_PROHIBIT_REMAIL
10093     if (cmailMailedMove) {
10094         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);
10095         DisplayError(msg, 0);
10096         return;
10097     }
10098 #endif
10099
10100     if (! (cmailMailedMove || RegisterMove())) return;
10101     
10102     if (   cmailMailedMove
10103         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10104         sprintf(string, partCommandString,
10105                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10106         commandOutput = popen(string, "r");
10107
10108         if (commandOutput == NULL) {
10109             DisplayError(_("Failed to invoke cmail"), 0);
10110         } else {
10111             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10112                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10113             }
10114             if (nBuffers > 1) {
10115                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10116                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10117                 nBytes = MSG_SIZ - 1;
10118             } else {
10119                 (void) memcpy(msg, buffer, nBytes);
10120             }
10121             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10122
10123             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10124                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10125
10126                 archived = TRUE;
10127                 for (i = 0; i < nCmailGames; i ++) {
10128                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10129                         archived = FALSE;
10130                     }
10131                 }
10132                 if (   archived
10133                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10134                         != NULL)) {
10135                     sprintf(buffer, "%s/%s.%s.archive",
10136                             arcDir,
10137                             appData.cmailGameName,
10138                             gameInfo.date);
10139                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10140                     cmailMsgLoaded = FALSE;
10141                 }
10142             }
10143
10144             DisplayInformation(msg);
10145             pclose(commandOutput);
10146         }
10147     } else {
10148         if ((*cmailMsg) != '\0') {
10149             DisplayInformation(cmailMsg);
10150         }
10151     }
10152
10153     return;
10154 #endif /* !WIN32 */
10155 }
10156
10157 char *
10158 CmailMsg()
10159 {
10160 #if WIN32
10161     return NULL;
10162 #else
10163     int  prependComma = 0;
10164     char number[5];
10165     char string[MSG_SIZ];       /* Space for game-list */
10166     int  i;
10167     
10168     if (!cmailMsgLoaded) return "";
10169
10170     if (cmailMailedMove) {
10171         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10172     } else {
10173         /* Create a list of games left */
10174         sprintf(string, "[");
10175         for (i = 0; i < nCmailGames; i ++) {
10176             if (! (   cmailMoveRegistered[i]
10177                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10178                 if (prependComma) {
10179                     sprintf(number, ",%d", i + 1);
10180                 } else {
10181                     sprintf(number, "%d", i + 1);
10182                     prependComma = 1;
10183                 }
10184                 
10185                 strcat(string, number);
10186             }
10187         }
10188         strcat(string, "]");
10189
10190         if (nCmailMovesRegistered + nCmailResults == 0) {
10191             switch (nCmailGames) {
10192               case 1:
10193                 sprintf(cmailMsg,
10194                         _("Still need to make move for game\n"));
10195                 break;
10196                 
10197               case 2:
10198                 sprintf(cmailMsg,
10199                         _("Still need to make moves for both games\n"));
10200                 break;
10201                 
10202               default:
10203                 sprintf(cmailMsg,
10204                         _("Still need to make moves for all %d games\n"),
10205                         nCmailGames);
10206                 break;
10207             }
10208         } else {
10209             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10210               case 1:
10211                 sprintf(cmailMsg,
10212                         _("Still need to make a move for game %s\n"),
10213                         string);
10214                 break;
10215                 
10216               case 0:
10217                 if (nCmailResults == nCmailGames) {
10218                     sprintf(cmailMsg, _("No unfinished games\n"));
10219                 } else {
10220                     sprintf(cmailMsg, _("Ready to send mail\n"));
10221                 }
10222                 break;
10223                 
10224               default:
10225                 sprintf(cmailMsg,
10226                         _("Still need to make moves for games %s\n"),
10227                         string);
10228             }
10229         }
10230     }
10231     return cmailMsg;
10232 #endif /* WIN32 */
10233 }
10234
10235 void
10236 ResetGameEvent()
10237 {
10238     if (gameMode == Training)
10239       SetTrainingModeOff();
10240
10241     Reset(TRUE, TRUE);
10242     cmailMsgLoaded = FALSE;
10243     if (appData.icsActive) {
10244       SendToICS(ics_prefix);
10245       SendToICS("refresh\n");
10246     }
10247 }
10248
10249 void
10250 ExitEvent(status)
10251      int status;
10252 {
10253     exiting++;
10254     if (exiting > 2) {
10255       /* Give up on clean exit */
10256       exit(status);
10257     }
10258     if (exiting > 1) {
10259       /* Keep trying for clean exit */
10260       return;
10261     }
10262
10263     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10264
10265     if (telnetISR != NULL) {
10266       RemoveInputSource(telnetISR);
10267     }
10268     if (icsPR != NoProc) {
10269       DestroyChildProcess(icsPR, TRUE);
10270     }
10271 #if 0
10272     /* Save game if resource set and not already saved by GameEnds() */
10273     if ((gameInfo.resultDetails == NULL || errorExitFlag )
10274                              && forwardMostMove > 0) {
10275       if (*appData.saveGameFile != NULLCHAR) {
10276         SaveGameToFile(appData.saveGameFile, TRUE);
10277       } else if (appData.autoSaveGames) {
10278         AutoSaveGame();
10279       }
10280       if (*appData.savePositionFile != NULLCHAR) {
10281         SavePositionToFile(appData.savePositionFile);
10282       }
10283     }
10284     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10285 #else
10286     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10287     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10288 #endif
10289     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10290     /* make sure this other one finishes before killing it!                  */
10291     if(endingGame) { int count = 0;
10292         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10293         while(endingGame && count++ < 10) DoSleep(1);
10294         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10295     }
10296
10297     /* Kill off chess programs */
10298     if (first.pr != NoProc) {
10299         ExitAnalyzeMode();
10300         
10301         DoSleep( appData.delayBeforeQuit );
10302         SendToProgram("quit\n", &first);
10303         DoSleep( appData.delayAfterQuit );
10304         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10305     }
10306     if (second.pr != NoProc) {
10307         DoSleep( appData.delayBeforeQuit );
10308         SendToProgram("quit\n", &second);
10309         DoSleep( appData.delayAfterQuit );
10310         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10311     }
10312     if (first.isr != NULL) {
10313         RemoveInputSource(first.isr);
10314     }
10315     if (second.isr != NULL) {
10316         RemoveInputSource(second.isr);
10317     }
10318
10319     ShutDownFrontEnd();
10320     exit(status);
10321 }
10322
10323 void
10324 PauseEvent()
10325 {
10326     if (appData.debugMode)
10327         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10328     if (pausing) {
10329         pausing = FALSE;
10330         ModeHighlight();
10331         if (gameMode == MachinePlaysWhite ||
10332             gameMode == MachinePlaysBlack) {
10333             StartClocks();
10334         } else {
10335             DisplayBothClocks();
10336         }
10337         if (gameMode == PlayFromGameFile) {
10338             if (appData.timeDelay >= 0) 
10339                 AutoPlayGameLoop();
10340         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10341             Reset(FALSE, TRUE);
10342             SendToICS(ics_prefix);
10343             SendToICS("refresh\n");
10344         } else if (currentMove < forwardMostMove) {
10345             ForwardInner(forwardMostMove);
10346         }
10347         pauseExamInvalid = FALSE;
10348     } else {
10349         switch (gameMode) {
10350           default:
10351             return;
10352           case IcsExamining:
10353             pauseExamForwardMostMove = forwardMostMove;
10354             pauseExamInvalid = FALSE;
10355             /* fall through */
10356           case IcsObserving:
10357           case IcsPlayingWhite:
10358           case IcsPlayingBlack:
10359             pausing = TRUE;
10360             ModeHighlight();
10361             return;
10362           case PlayFromGameFile:
10363             (void) StopLoadGameTimer();
10364             pausing = TRUE;
10365             ModeHighlight();
10366             break;
10367           case BeginningOfGame:
10368             if (appData.icsActive) return;
10369             /* else fall through */
10370           case MachinePlaysWhite:
10371           case MachinePlaysBlack:
10372           case TwoMachinesPlay:
10373             if (forwardMostMove == 0)
10374               return;           /* don't pause if no one has moved */
10375             if ((gameMode == MachinePlaysWhite &&
10376                  !WhiteOnMove(forwardMostMove)) ||
10377                 (gameMode == MachinePlaysBlack &&
10378                  WhiteOnMove(forwardMostMove))) {
10379                 StopClocks();
10380             }
10381             pausing = TRUE;
10382             ModeHighlight();
10383             break;
10384         }
10385     }
10386 }
10387
10388 void
10389 EditCommentEvent()
10390 {
10391     char title[MSG_SIZ];
10392
10393     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10394         strcpy(title, _("Edit comment"));
10395     } else {
10396         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10397                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10398                 parseList[currentMove - 1]);
10399     }
10400
10401     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10402 }
10403
10404
10405 void
10406 EditTagsEvent()
10407 {
10408     char *tags = PGNTags(&gameInfo);
10409     EditTagsPopUp(tags);
10410     free(tags);
10411 }
10412
10413 void
10414 AnalyzeModeEvent()
10415 {
10416     if (appData.noChessProgram || gameMode == AnalyzeMode)
10417       return;
10418
10419     if (gameMode != AnalyzeFile) {
10420         if (!appData.icsEngineAnalyze) {
10421                EditGameEvent();
10422                if (gameMode != EditGame) return;
10423         }
10424         ResurrectChessProgram();
10425         SendToProgram("analyze\n", &first);
10426         first.analyzing = TRUE;
10427         /*first.maybeThinking = TRUE;*/
10428         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10429         AnalysisPopUp(_("Analysis"),
10430                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10431     }
10432     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10433     pausing = FALSE;
10434     ModeHighlight();
10435     SetGameInfo();
10436
10437     StartAnalysisClock();
10438     GetTimeMark(&lastNodeCountTime);
10439     lastNodeCount = 0;
10440 }
10441
10442 void
10443 AnalyzeFileEvent()
10444 {
10445     if (appData.noChessProgram || gameMode == AnalyzeFile)
10446       return;
10447
10448     if (gameMode != AnalyzeMode) {
10449         EditGameEvent();
10450         if (gameMode != EditGame) return;
10451         ResurrectChessProgram();
10452         SendToProgram("analyze\n", &first);
10453         first.analyzing = TRUE;
10454         /*first.maybeThinking = TRUE;*/
10455         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10456         AnalysisPopUp(_("Analysis"),
10457                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10458     }
10459     gameMode = AnalyzeFile;
10460     pausing = FALSE;
10461     ModeHighlight();
10462     SetGameInfo();
10463
10464     StartAnalysisClock();
10465     GetTimeMark(&lastNodeCountTime);
10466     lastNodeCount = 0;
10467 }
10468
10469 void
10470 MachineWhiteEvent()
10471 {
10472     char buf[MSG_SIZ];
10473     char *bookHit = NULL;
10474
10475     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10476       return;
10477
10478
10479     if (gameMode == PlayFromGameFile || 
10480         gameMode == TwoMachinesPlay  || 
10481         gameMode == Training         || 
10482         gameMode == AnalyzeMode      || 
10483         gameMode == EndOfGame)
10484         EditGameEvent();
10485
10486     if (gameMode == EditPosition) 
10487         EditPositionDone();
10488
10489     if (!WhiteOnMove(currentMove)) {
10490         DisplayError(_("It is not White's turn"), 0);
10491         return;
10492     }
10493   
10494     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10495       ExitAnalyzeMode();
10496
10497     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10498         gameMode == AnalyzeFile)
10499         TruncateGame();
10500
10501     ResurrectChessProgram();    /* in case it isn't running */
10502     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10503         gameMode = MachinePlaysWhite;
10504         ResetClocks();
10505     } else
10506     gameMode = MachinePlaysWhite;
10507     pausing = FALSE;
10508     ModeHighlight();
10509     SetGameInfo();
10510     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10511     DisplayTitle(buf);
10512     if (first.sendName) {
10513       sprintf(buf, "name %s\n", gameInfo.black);
10514       SendToProgram(buf, &first);
10515     }
10516     if (first.sendTime) {
10517       if (first.useColors) {
10518         SendToProgram("black\n", &first); /*gnu kludge*/
10519       }
10520       SendTimeRemaining(&first, TRUE);
10521     }
10522     if (first.useColors) {
10523       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10524     }
10525     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10526     SetMachineThinkingEnables();
10527     first.maybeThinking = TRUE;
10528     StartClocks();
10529
10530     if (appData.autoFlipView && !flipView) {
10531       flipView = !flipView;
10532       DrawPosition(FALSE, NULL);
10533       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10534     }
10535
10536     if(bookHit) { // [HGM] book: simulate book reply
10537         static char bookMove[MSG_SIZ]; // a bit generous?
10538
10539         programStats.nodes = programStats.depth = programStats.time = 
10540         programStats.score = programStats.got_only_move = 0;
10541         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10542
10543         strcpy(bookMove, "move ");
10544         strcat(bookMove, bookHit);
10545         HandleMachineMove(bookMove, &first);
10546     }
10547 }
10548
10549 void
10550 MachineBlackEvent()
10551 {
10552     char buf[MSG_SIZ];
10553    char *bookHit = NULL;
10554
10555     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10556         return;
10557
10558
10559     if (gameMode == PlayFromGameFile || 
10560         gameMode == TwoMachinesPlay  || 
10561         gameMode == Training         || 
10562         gameMode == AnalyzeMode      || 
10563         gameMode == EndOfGame)
10564         EditGameEvent();
10565
10566     if (gameMode == EditPosition) 
10567         EditPositionDone();
10568
10569     if (WhiteOnMove(currentMove)) {
10570         DisplayError(_("It is not Black's turn"), 0);
10571         return;
10572     }
10573     
10574     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10575       ExitAnalyzeMode();
10576
10577     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10578         gameMode == AnalyzeFile)
10579         TruncateGame();
10580
10581     ResurrectChessProgram();    /* in case it isn't running */
10582     gameMode = MachinePlaysBlack;
10583     pausing = FALSE;
10584     ModeHighlight();
10585     SetGameInfo();
10586     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10587     DisplayTitle(buf);
10588     if (first.sendName) {
10589       sprintf(buf, "name %s\n", gameInfo.white);
10590       SendToProgram(buf, &first);
10591     }
10592     if (first.sendTime) {
10593       if (first.useColors) {
10594         SendToProgram("white\n", &first); /*gnu kludge*/
10595       }
10596       SendTimeRemaining(&first, FALSE);
10597     }
10598     if (first.useColors) {
10599       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10600     }
10601     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10602     SetMachineThinkingEnables();
10603     first.maybeThinking = TRUE;
10604     StartClocks();
10605
10606     if (appData.autoFlipView && flipView) {
10607       flipView = !flipView;
10608       DrawPosition(FALSE, NULL);
10609       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10610     }
10611     if(bookHit) { // [HGM] book: simulate book reply
10612         static char bookMove[MSG_SIZ]; // a bit generous?
10613
10614         programStats.nodes = programStats.depth = programStats.time = 
10615         programStats.score = programStats.got_only_move = 0;
10616         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10617
10618         strcpy(bookMove, "move ");
10619         strcat(bookMove, bookHit);
10620         HandleMachineMove(bookMove, &first);
10621     }
10622 }
10623
10624
10625 void
10626 DisplayTwoMachinesTitle()
10627 {
10628     char buf[MSG_SIZ];
10629     if (appData.matchGames > 0) {
10630         if (first.twoMachinesColor[0] == 'w') {
10631             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10632                     gameInfo.white, gameInfo.black,
10633                     first.matchWins, second.matchWins,
10634                     matchGame - 1 - (first.matchWins + second.matchWins));
10635         } else {
10636             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10637                     gameInfo.white, gameInfo.black,
10638                     second.matchWins, first.matchWins,
10639                     matchGame - 1 - (first.matchWins + second.matchWins));
10640         }
10641     } else {
10642         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10643     }
10644     DisplayTitle(buf);
10645 }
10646
10647 void
10648 TwoMachinesEvent P((void))
10649 {
10650     int i;
10651     char buf[MSG_SIZ];
10652     ChessProgramState *onmove;
10653     char *bookHit = NULL;
10654     
10655     if (appData.noChessProgram) return;
10656
10657     switch (gameMode) {
10658       case TwoMachinesPlay:
10659         return;
10660       case MachinePlaysWhite:
10661       case MachinePlaysBlack:
10662         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10663             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10664             return;
10665         }
10666         /* fall through */
10667       case BeginningOfGame:
10668       case PlayFromGameFile:
10669       case EndOfGame:
10670         EditGameEvent();
10671         if (gameMode != EditGame) return;
10672         break;
10673       case EditPosition:
10674         EditPositionDone();
10675         break;
10676       case AnalyzeMode:
10677       case AnalyzeFile:
10678         ExitAnalyzeMode();
10679         break;
10680       case EditGame:
10681       default:
10682         break;
10683     }
10684
10685     forwardMostMove = currentMove;
10686     ResurrectChessProgram();    /* in case first program isn't running */
10687
10688     if (second.pr == NULL) {
10689         StartChessProgram(&second);
10690         if (second.protocolVersion == 1) {
10691           TwoMachinesEventIfReady();
10692         } else {
10693           /* kludge: allow timeout for initial "feature" command */
10694           FreezeUI();
10695           DisplayMessage("", _("Starting second chess program"));
10696           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10697         }
10698         return;
10699     }
10700     DisplayMessage("", "");
10701     InitChessProgram(&second, FALSE);
10702     SendToProgram("force\n", &second);
10703     if (startedFromSetupPosition) {
10704         SendBoard(&second, backwardMostMove);
10705     if (appData.debugMode) {
10706         fprintf(debugFP, "Two Machines\n");
10707     }
10708     }
10709     for (i = backwardMostMove; i < forwardMostMove; i++) {
10710         SendMoveToProgram(i, &second);
10711     }
10712
10713     gameMode = TwoMachinesPlay;
10714     pausing = FALSE;
10715     ModeHighlight();
10716     SetGameInfo();
10717     DisplayTwoMachinesTitle();
10718     firstMove = TRUE;
10719     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10720         onmove = &first;
10721     } else {
10722         onmove = &second;
10723     }
10724
10725     SendToProgram(first.computerString, &first);
10726     if (first.sendName) {
10727       sprintf(buf, "name %s\n", second.tidy);
10728       SendToProgram(buf, &first);
10729     }
10730     SendToProgram(second.computerString, &second);
10731     if (second.sendName) {
10732       sprintf(buf, "name %s\n", first.tidy);
10733       SendToProgram(buf, &second);
10734     }
10735
10736     ResetClocks();
10737     if (!first.sendTime || !second.sendTime) {
10738         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10739         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10740     }
10741     if (onmove->sendTime) {
10742       if (onmove->useColors) {
10743         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10744       }
10745       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10746     }
10747     if (onmove->useColors) {
10748       SendToProgram(onmove->twoMachinesColor, onmove);
10749     }
10750     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10751 //    SendToProgram("go\n", onmove);
10752     onmove->maybeThinking = TRUE;
10753     SetMachineThinkingEnables();
10754
10755     StartClocks();
10756
10757     if(bookHit) { // [HGM] book: simulate book reply
10758         static char bookMove[MSG_SIZ]; // a bit generous?
10759
10760         programStats.nodes = programStats.depth = programStats.time = 
10761         programStats.score = programStats.got_only_move = 0;
10762         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10763
10764         strcpy(bookMove, "move ");
10765         strcat(bookMove, bookHit);
10766         HandleMachineMove(bookMove, &first);
10767     }
10768 }
10769
10770 void
10771 TrainingEvent()
10772 {
10773     if (gameMode == Training) {
10774       SetTrainingModeOff();
10775       gameMode = PlayFromGameFile;
10776       DisplayMessage("", _("Training mode off"));
10777     } else {
10778       gameMode = Training;
10779       animateTraining = appData.animate;
10780
10781       /* make sure we are not already at the end of the game */
10782       if (currentMove < forwardMostMove) {
10783         SetTrainingModeOn();
10784         DisplayMessage("", _("Training mode on"));
10785       } else {
10786         gameMode = PlayFromGameFile;
10787         DisplayError(_("Already at end of game"), 0);
10788       }
10789     }
10790     ModeHighlight();
10791 }
10792
10793 void
10794 IcsClientEvent()
10795 {
10796     if (!appData.icsActive) return;
10797     switch (gameMode) {
10798       case IcsPlayingWhite:
10799       case IcsPlayingBlack:
10800       case IcsObserving:
10801       case IcsIdle:
10802       case BeginningOfGame:
10803       case IcsExamining:
10804         return;
10805
10806       case EditGame:
10807         break;
10808
10809       case EditPosition:
10810         EditPositionDone();
10811         break;
10812
10813       case AnalyzeMode:
10814       case AnalyzeFile:
10815         ExitAnalyzeMode();
10816         break;
10817         
10818       default:
10819         EditGameEvent();
10820         break;
10821     }
10822
10823     gameMode = IcsIdle;
10824     ModeHighlight();
10825     return;
10826 }
10827
10828
10829 void
10830 EditGameEvent()
10831 {
10832     int i;
10833
10834     switch (gameMode) {
10835       case Training:
10836         SetTrainingModeOff();
10837         break;
10838       case MachinePlaysWhite:
10839       case MachinePlaysBlack:
10840       case BeginningOfGame:
10841         SendToProgram("force\n", &first);
10842         SetUserThinkingEnables();
10843         break;
10844       case PlayFromGameFile:
10845         (void) StopLoadGameTimer();
10846         if (gameFileFP != NULL) {
10847             gameFileFP = NULL;
10848         }
10849         break;
10850       case EditPosition:
10851         EditPositionDone();
10852         break;
10853       case AnalyzeMode:
10854       case AnalyzeFile:
10855         ExitAnalyzeMode();
10856         SendToProgram("force\n", &first);
10857         break;
10858       case TwoMachinesPlay:
10859         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10860         ResurrectChessProgram();
10861         SetUserThinkingEnables();
10862         break;
10863       case EndOfGame:
10864         ResurrectChessProgram();
10865         break;
10866       case IcsPlayingBlack:
10867       case IcsPlayingWhite:
10868         DisplayError(_("Warning: You are still playing a game"), 0);
10869         break;
10870       case IcsObserving:
10871         DisplayError(_("Warning: You are still observing a game"), 0);
10872         break;
10873       case IcsExamining:
10874         DisplayError(_("Warning: You are still examining a game"), 0);
10875         break;
10876       case IcsIdle:
10877         break;
10878       case EditGame:
10879       default:
10880         return;
10881     }
10882     
10883     pausing = FALSE;
10884     StopClocks();
10885     first.offeredDraw = second.offeredDraw = 0;
10886
10887     if (gameMode == PlayFromGameFile) {
10888         whiteTimeRemaining = timeRemaining[0][currentMove];
10889         blackTimeRemaining = timeRemaining[1][currentMove];
10890         DisplayTitle("");
10891     }
10892
10893     if (gameMode == MachinePlaysWhite ||
10894         gameMode == MachinePlaysBlack ||
10895         gameMode == TwoMachinesPlay ||
10896         gameMode == EndOfGame) {
10897         i = forwardMostMove;
10898         while (i > currentMove) {
10899             SendToProgram("undo\n", &first);
10900             i--;
10901         }
10902         whiteTimeRemaining = timeRemaining[0][currentMove];
10903         blackTimeRemaining = timeRemaining[1][currentMove];
10904         DisplayBothClocks();
10905         if (whiteFlag || blackFlag) {
10906             whiteFlag = blackFlag = 0;
10907         }
10908         DisplayTitle("");
10909     }           
10910     
10911     gameMode = EditGame;
10912     ModeHighlight();
10913     SetGameInfo();
10914 }
10915
10916
10917 void
10918 EditPositionEvent()
10919 {
10920     if (gameMode == EditPosition) {
10921         EditGameEvent();
10922         return;
10923     }
10924     
10925     EditGameEvent();
10926     if (gameMode != EditGame) return;
10927     
10928     gameMode = EditPosition;
10929     ModeHighlight();
10930     SetGameInfo();
10931     if (currentMove > 0)
10932       CopyBoard(boards[0], boards[currentMove]);
10933     
10934     blackPlaysFirst = !WhiteOnMove(currentMove);
10935     ResetClocks();
10936     currentMove = forwardMostMove = backwardMostMove = 0;
10937     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10938     DisplayMove(-1);
10939 }
10940
10941 void
10942 ExitAnalyzeMode()
10943 {
10944     /* [DM] icsEngineAnalyze - possible call from other functions */
10945     if (appData.icsEngineAnalyze) {
10946         appData.icsEngineAnalyze = FALSE;
10947
10948         DisplayMessage("",_("Close ICS engine analyze..."));
10949     }
10950     if (first.analysisSupport && first.analyzing) {
10951       SendToProgram("exit\n", &first);
10952       first.analyzing = FALSE;
10953     }
10954     AnalysisPopDown();
10955     thinkOutput[0] = NULLCHAR;
10956 }
10957
10958 void
10959 EditPositionDone()
10960 {
10961     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
10962
10963     startedFromSetupPosition = TRUE;
10964     InitChessProgram(&first, FALSE);
10965     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
10966     if(boards[0][0][BOARD_WIDTH>>1] == king) {
10967         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
10968         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
10969     } else castlingRights[0][2] = -1;
10970     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
10971         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
10972         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
10973     } else castlingRights[0][5] = -1;
10974     SendToProgram("force\n", &first);
10975     if (blackPlaysFirst) {
10976         strcpy(moveList[0], "");
10977         strcpy(parseList[0], "");
10978         currentMove = forwardMostMove = backwardMostMove = 1;
10979         CopyBoard(boards[1], boards[0]);
10980         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10981         { int i;
10982           epStatus[1] = epStatus[0];
10983           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10984         }
10985     } else {
10986         currentMove = forwardMostMove = backwardMostMove = 0;
10987     }
10988     SendBoard(&first, forwardMostMove);
10989     if (appData.debugMode) {
10990         fprintf(debugFP, "EditPosDone\n");
10991     }
10992     DisplayTitle("");
10993     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10994     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10995     gameMode = EditGame;
10996     ModeHighlight();
10997     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10998     ClearHighlights(); /* [AS] */
10999 }
11000
11001 /* Pause for `ms' milliseconds */
11002 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11003 void
11004 TimeDelay(ms)
11005      long ms;
11006 {
11007     TimeMark m1, m2;
11008
11009     GetTimeMark(&m1);
11010     do {
11011         GetTimeMark(&m2);
11012     } while (SubtractTimeMarks(&m2, &m1) < ms);
11013 }
11014
11015 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11016 void
11017 SendMultiLineToICS(buf)
11018      char *buf;
11019 {
11020     char temp[MSG_SIZ+1], *p;
11021     int len;
11022
11023     len = strlen(buf);
11024     if (len > MSG_SIZ)
11025       len = MSG_SIZ;
11026   
11027     strncpy(temp, buf, len);
11028     temp[len] = 0;
11029
11030     p = temp;
11031     while (*p) {
11032         if (*p == '\n' || *p == '\r')
11033           *p = ' ';
11034         ++p;
11035     }
11036
11037     strcat(temp, "\n");
11038     SendToICS(temp);
11039     SendToPlayer(temp, strlen(temp));
11040 }
11041
11042 void
11043 SetWhiteToPlayEvent()
11044 {
11045     if (gameMode == EditPosition) {
11046         blackPlaysFirst = FALSE;
11047         DisplayBothClocks();    /* works because currentMove is 0 */
11048     } else if (gameMode == IcsExamining) {
11049         SendToICS(ics_prefix);
11050         SendToICS("tomove white\n");
11051     }
11052 }
11053
11054 void
11055 SetBlackToPlayEvent()
11056 {
11057     if (gameMode == EditPosition) {
11058         blackPlaysFirst = TRUE;
11059         currentMove = 1;        /* kludge */
11060         DisplayBothClocks();
11061         currentMove = 0;
11062     } else if (gameMode == IcsExamining) {
11063         SendToICS(ics_prefix);
11064         SendToICS("tomove black\n");
11065     }
11066 }
11067
11068 void
11069 EditPositionMenuEvent(selection, x, y)
11070      ChessSquare selection;
11071      int x, y;
11072 {
11073     char buf[MSG_SIZ];
11074     ChessSquare piece = boards[0][y][x];
11075
11076     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11077
11078     switch (selection) {
11079       case ClearBoard:
11080         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11081             SendToICS(ics_prefix);
11082             SendToICS("bsetup clear\n");
11083         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11084             SendToICS(ics_prefix);
11085             SendToICS("clearboard\n");
11086         } else {
11087             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11088                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11089                 for (y = 0; y < BOARD_HEIGHT; y++) {
11090                     if (gameMode == IcsExamining) {
11091                         if (boards[currentMove][y][x] != EmptySquare) {
11092                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11093                                     AAA + x, ONE + y);
11094                             SendToICS(buf);
11095                         }
11096                     } else {
11097                         boards[0][y][x] = p;
11098                     }
11099                 }
11100             }
11101         }
11102         if (gameMode == EditPosition) {
11103             DrawPosition(FALSE, boards[0]);
11104         }
11105         break;
11106
11107       case WhitePlay:
11108         SetWhiteToPlayEvent();
11109         break;
11110
11111       case BlackPlay:
11112         SetBlackToPlayEvent();
11113         break;
11114
11115       case EmptySquare:
11116         if (gameMode == IcsExamining) {
11117             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11118             SendToICS(buf);
11119         } else {
11120             boards[0][y][x] = EmptySquare;
11121             DrawPosition(FALSE, boards[0]);
11122         }
11123         break;
11124
11125       case PromotePiece:
11126         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11127            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11128             selection = (ChessSquare) (PROMOTED piece);
11129         } else if(piece == EmptySquare) selection = WhiteSilver;
11130         else selection = (ChessSquare)((int)piece - 1);
11131         goto defaultlabel;
11132
11133       case DemotePiece:
11134         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11135            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11136             selection = (ChessSquare) (DEMOTED piece);
11137         } else if(piece == EmptySquare) selection = BlackSilver;
11138         else selection = (ChessSquare)((int)piece + 1);       
11139         goto defaultlabel;
11140
11141       case WhiteQueen:
11142       case BlackQueen:
11143         if(gameInfo.variant == VariantShatranj ||
11144            gameInfo.variant == VariantXiangqi  ||
11145            gameInfo.variant == VariantCourier    )
11146             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11147         goto defaultlabel;
11148
11149       case WhiteKing:
11150       case BlackKing:
11151         if(gameInfo.variant == VariantXiangqi)
11152             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11153         if(gameInfo.variant == VariantKnightmate)
11154             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11155       default:
11156         defaultlabel:
11157         if (gameMode == IcsExamining) {
11158             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11159                     PieceToChar(selection), AAA + x, ONE + y);
11160             SendToICS(buf);
11161         } else {
11162             boards[0][y][x] = selection;
11163             DrawPosition(FALSE, boards[0]);
11164         }
11165         break;
11166     }
11167 }
11168
11169
11170 void
11171 DropMenuEvent(selection, x, y)
11172      ChessSquare selection;
11173      int x, y;
11174 {
11175     ChessMove moveType;
11176
11177     switch (gameMode) {
11178       case IcsPlayingWhite:
11179       case MachinePlaysBlack:
11180         if (!WhiteOnMove(currentMove)) {
11181             DisplayMoveError(_("It is Black's turn"));
11182             return;
11183         }
11184         moveType = WhiteDrop;
11185         break;
11186       case IcsPlayingBlack:
11187       case MachinePlaysWhite:
11188         if (WhiteOnMove(currentMove)) {
11189             DisplayMoveError(_("It is White's turn"));
11190             return;
11191         }
11192         moveType = BlackDrop;
11193         break;
11194       case EditGame:
11195         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11196         break;
11197       default:
11198         return;
11199     }
11200
11201     if (moveType == BlackDrop && selection < BlackPawn) {
11202       selection = (ChessSquare) ((int) selection
11203                                  + (int) BlackPawn - (int) WhitePawn);
11204     }
11205     if (boards[currentMove][y][x] != EmptySquare) {
11206         DisplayMoveError(_("That square is occupied"));
11207         return;
11208     }
11209
11210     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11211 }
11212
11213 void
11214 AcceptEvent()
11215 {
11216     /* Accept a pending offer of any kind from opponent */
11217     
11218     if (appData.icsActive) {
11219         SendToICS(ics_prefix);
11220         SendToICS("accept\n");
11221     } else if (cmailMsgLoaded) {
11222         if (currentMove == cmailOldMove &&
11223             commentList[cmailOldMove] != NULL &&
11224             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11225                    "Black offers a draw" : "White offers a draw")) {
11226             TruncateGame();
11227             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11228             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11229         } else {
11230             DisplayError(_("There is no pending offer on this move"), 0);
11231             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11232         }
11233     } else {
11234         /* Not used for offers from chess program */
11235     }
11236 }
11237
11238 void
11239 DeclineEvent()
11240 {
11241     /* Decline a pending offer of any kind from opponent */
11242     
11243     if (appData.icsActive) {
11244         SendToICS(ics_prefix);
11245         SendToICS("decline\n");
11246     } else if (cmailMsgLoaded) {
11247         if (currentMove == cmailOldMove &&
11248             commentList[cmailOldMove] != NULL &&
11249             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11250                    "Black offers a draw" : "White offers a draw")) {
11251 #ifdef NOTDEF
11252             AppendComment(cmailOldMove, "Draw declined");
11253             DisplayComment(cmailOldMove - 1, "Draw declined");
11254 #endif /*NOTDEF*/
11255         } else {
11256             DisplayError(_("There is no pending offer on this move"), 0);
11257         }
11258     } else {
11259         /* Not used for offers from chess program */
11260     }
11261 }
11262
11263 void
11264 RematchEvent()
11265 {
11266     /* Issue ICS rematch command */
11267     if (appData.icsActive) {
11268         SendToICS(ics_prefix);
11269         SendToICS("rematch\n");
11270     }
11271 }
11272
11273 void
11274 CallFlagEvent()
11275 {
11276     /* Call your opponent's flag (claim a win on time) */
11277     if (appData.icsActive) {
11278         SendToICS(ics_prefix);
11279         SendToICS("flag\n");
11280     } else {
11281         switch (gameMode) {
11282           default:
11283             return;
11284           case MachinePlaysWhite:
11285             if (whiteFlag) {
11286                 if (blackFlag)
11287                   GameEnds(GameIsDrawn, "Both players ran out of time",
11288                            GE_PLAYER);
11289                 else
11290                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11291             } else {
11292                 DisplayError(_("Your opponent is not out of time"), 0);
11293             }
11294             break;
11295           case MachinePlaysBlack:
11296             if (blackFlag) {
11297                 if (whiteFlag)
11298                   GameEnds(GameIsDrawn, "Both players ran out of time",
11299                            GE_PLAYER);
11300                 else
11301                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11302             } else {
11303                 DisplayError(_("Your opponent is not out of time"), 0);
11304             }
11305             break;
11306         }
11307     }
11308 }
11309
11310 void
11311 DrawEvent()
11312 {
11313     /* Offer draw or accept pending draw offer from opponent */
11314     
11315     if (appData.icsActive) {
11316         /* Note: tournament rules require draw offers to be
11317            made after you make your move but before you punch
11318            your clock.  Currently ICS doesn't let you do that;
11319            instead, you immediately punch your clock after making
11320            a move, but you can offer a draw at any time. */
11321         
11322         SendToICS(ics_prefix);
11323         SendToICS("draw\n");
11324     } else if (cmailMsgLoaded) {
11325         if (currentMove == cmailOldMove &&
11326             commentList[cmailOldMove] != NULL &&
11327             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11328                    "Black offers a draw" : "White offers a draw")) {
11329             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11330             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11331         } else if (currentMove == cmailOldMove + 1) {
11332             char *offer = WhiteOnMove(cmailOldMove) ?
11333               "White offers a draw" : "Black offers a draw";
11334             AppendComment(currentMove, offer);
11335             DisplayComment(currentMove - 1, offer);
11336             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11337         } else {
11338             DisplayError(_("You must make your move before offering a draw"), 0);
11339             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11340         }
11341     } else if (first.offeredDraw) {
11342         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11343     } else {
11344         if (first.sendDrawOffers) {
11345             SendToProgram("draw\n", &first);
11346             userOfferedDraw = TRUE;
11347         }
11348     }
11349 }
11350
11351 void
11352 AdjournEvent()
11353 {
11354     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11355     
11356     if (appData.icsActive) {
11357         SendToICS(ics_prefix);
11358         SendToICS("adjourn\n");
11359     } else {
11360         /* Currently GNU Chess doesn't offer or accept Adjourns */
11361     }
11362 }
11363
11364
11365 void
11366 AbortEvent()
11367 {
11368     /* Offer Abort or accept pending Abort offer from opponent */
11369     
11370     if (appData.icsActive) {
11371         SendToICS(ics_prefix);
11372         SendToICS("abort\n");
11373     } else {
11374         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11375     }
11376 }
11377
11378 void
11379 ResignEvent()
11380 {
11381     /* Resign.  You can do this even if it's not your turn. */
11382     
11383     if (appData.icsActive) {
11384         SendToICS(ics_prefix);
11385         SendToICS("resign\n");
11386     } else {
11387         switch (gameMode) {
11388           case MachinePlaysWhite:
11389             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11390             break;
11391           case MachinePlaysBlack:
11392             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11393             break;
11394           case EditGame:
11395             if (cmailMsgLoaded) {
11396                 TruncateGame();
11397                 if (WhiteOnMove(cmailOldMove)) {
11398                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11399                 } else {
11400                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11401                 }
11402                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11403             }
11404             break;
11405           default:
11406             break;
11407         }
11408     }
11409 }
11410
11411
11412 void
11413 StopObservingEvent()
11414 {
11415     /* Stop observing current games */
11416     SendToICS(ics_prefix);
11417     SendToICS("unobserve\n");
11418 }
11419
11420 void
11421 StopExaminingEvent()
11422 {
11423     /* Stop observing current game */
11424     SendToICS(ics_prefix);
11425     SendToICS("unexamine\n");
11426 }
11427
11428 void
11429 ForwardInner(target)
11430      int target;
11431 {
11432     int limit;
11433
11434     if (appData.debugMode)
11435         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11436                 target, currentMove, forwardMostMove);
11437
11438     if (gameMode == EditPosition)
11439       return;
11440
11441     if (gameMode == PlayFromGameFile && !pausing)
11442       PauseEvent();
11443     
11444     if (gameMode == IcsExamining && pausing)
11445       limit = pauseExamForwardMostMove;
11446     else
11447       limit = forwardMostMove;
11448     
11449     if (target > limit) target = limit;
11450
11451     if (target > 0 && moveList[target - 1][0]) {
11452         int fromX, fromY, toX, toY;
11453         toX = moveList[target - 1][2] - AAA;
11454         toY = moveList[target - 1][3] - ONE;
11455         if (moveList[target - 1][1] == '@') {
11456             if (appData.highlightLastMove) {
11457                 SetHighlights(-1, -1, toX, toY);
11458             }
11459         } else {
11460             fromX = moveList[target - 1][0] - AAA;
11461             fromY = moveList[target - 1][1] - ONE;
11462             if (target == currentMove + 1) {
11463                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11464             }
11465             if (appData.highlightLastMove) {
11466                 SetHighlights(fromX, fromY, toX, toY);
11467             }
11468         }
11469     }
11470     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11471         gameMode == Training || gameMode == PlayFromGameFile || 
11472         gameMode == AnalyzeFile) {
11473         while (currentMove < target) {
11474             SendMoveToProgram(currentMove++, &first);
11475         }
11476     } else {
11477         currentMove = target;
11478     }
11479     
11480     if (gameMode == EditGame || gameMode == EndOfGame) {
11481         whiteTimeRemaining = timeRemaining[0][currentMove];
11482         blackTimeRemaining = timeRemaining[1][currentMove];
11483     }
11484     DisplayBothClocks();
11485     DisplayMove(currentMove - 1);
11486     DrawPosition(FALSE, boards[currentMove]);
11487     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11488     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11489         DisplayComment(currentMove - 1, commentList[currentMove]);
11490     }
11491 }
11492
11493
11494 void
11495 ForwardEvent()
11496 {
11497     if (gameMode == IcsExamining && !pausing) {
11498         SendToICS(ics_prefix);
11499         SendToICS("forward\n");
11500     } else {
11501         ForwardInner(currentMove + 1);
11502     }
11503 }
11504
11505 void
11506 ToEndEvent()
11507 {
11508     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11509         /* to optimze, we temporarily turn off analysis mode while we feed
11510          * the remaining moves to the engine. Otherwise we get analysis output
11511          * after each move.
11512          */ 
11513         if (first.analysisSupport) {
11514           SendToProgram("exit\nforce\n", &first);
11515           first.analyzing = FALSE;
11516         }
11517     }
11518         
11519     if (gameMode == IcsExamining && !pausing) {
11520         SendToICS(ics_prefix);
11521         SendToICS("forward 999999\n");
11522     } else {
11523         ForwardInner(forwardMostMove);
11524     }
11525
11526     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11527         /* we have fed all the moves, so reactivate analysis mode */
11528         SendToProgram("analyze\n", &first);
11529         first.analyzing = TRUE;
11530         /*first.maybeThinking = TRUE;*/
11531         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11532     }
11533 }
11534
11535 void
11536 BackwardInner(target)
11537      int target;
11538 {
11539     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11540
11541     if (appData.debugMode)
11542         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11543                 target, currentMove, forwardMostMove);
11544
11545     if (gameMode == EditPosition) return;
11546     if (currentMove <= backwardMostMove) {
11547         ClearHighlights();
11548         DrawPosition(full_redraw, boards[currentMove]);
11549         return;
11550     }
11551     if (gameMode == PlayFromGameFile && !pausing)
11552       PauseEvent();
11553     
11554     if (moveList[target][0]) {
11555         int fromX, fromY, toX, toY;
11556         toX = moveList[target][2] - AAA;
11557         toY = moveList[target][3] - ONE;
11558         if (moveList[target][1] == '@') {
11559             if (appData.highlightLastMove) {
11560                 SetHighlights(-1, -1, toX, toY);
11561             }
11562         } else {
11563             fromX = moveList[target][0] - AAA;
11564             fromY = moveList[target][1] - ONE;
11565             if (target == currentMove - 1) {
11566                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11567             }
11568             if (appData.highlightLastMove) {
11569                 SetHighlights(fromX, fromY, toX, toY);
11570             }
11571         }
11572     }
11573     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11574         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11575         while (currentMove > target) {
11576             SendToProgram("undo\n", &first);
11577             currentMove--;
11578         }
11579     } else {
11580         currentMove = target;
11581     }
11582     
11583     if (gameMode == EditGame || gameMode == EndOfGame) {
11584         whiteTimeRemaining = timeRemaining[0][currentMove];
11585         blackTimeRemaining = timeRemaining[1][currentMove];
11586     }
11587     DisplayBothClocks();
11588     DisplayMove(currentMove - 1);
11589     DrawPosition(full_redraw, boards[currentMove]);
11590     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11591     // [HGM] PV info: routine tests if comment empty
11592     DisplayComment(currentMove - 1, commentList[currentMove]);
11593 }
11594
11595 void
11596 BackwardEvent()
11597 {
11598     if (gameMode == IcsExamining && !pausing) {
11599         SendToICS(ics_prefix);
11600         SendToICS("backward\n");
11601     } else {
11602         BackwardInner(currentMove - 1);
11603     }
11604 }
11605
11606 void
11607 ToStartEvent()
11608 {
11609     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11610         /* to optimze, we temporarily turn off analysis mode while we undo
11611          * all the moves. Otherwise we get analysis output after each undo.
11612          */ 
11613         if (first.analysisSupport) {
11614           SendToProgram("exit\nforce\n", &first);
11615           first.analyzing = FALSE;
11616         }
11617     }
11618
11619     if (gameMode == IcsExamining && !pausing) {
11620         SendToICS(ics_prefix);
11621         SendToICS("backward 999999\n");
11622     } else {
11623         BackwardInner(backwardMostMove);
11624     }
11625
11626     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11627         /* we have fed all the moves, so reactivate analysis mode */
11628         SendToProgram("analyze\n", &first);
11629         first.analyzing = TRUE;
11630         /*first.maybeThinking = TRUE;*/
11631         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11632     }
11633 }
11634
11635 void
11636 ToNrEvent(int to)
11637 {
11638   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11639   if (to >= forwardMostMove) to = forwardMostMove;
11640   if (to <= backwardMostMove) to = backwardMostMove;
11641   if (to < currentMove) {
11642     BackwardInner(to);
11643   } else {
11644     ForwardInner(to);
11645   }
11646 }
11647
11648 void
11649 RevertEvent()
11650 {
11651     if (gameMode != IcsExamining) {
11652         DisplayError(_("You are not examining a game"), 0);
11653         return;
11654     }
11655     if (pausing) {
11656         DisplayError(_("You can't revert while pausing"), 0);
11657         return;
11658     }
11659     SendToICS(ics_prefix);
11660     SendToICS("revert\n");
11661 }
11662
11663 void
11664 RetractMoveEvent()
11665 {
11666     switch (gameMode) {
11667       case MachinePlaysWhite:
11668       case MachinePlaysBlack:
11669         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11670             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11671             return;
11672         }
11673         if (forwardMostMove < 2) return;
11674         currentMove = forwardMostMove = forwardMostMove - 2;
11675         whiteTimeRemaining = timeRemaining[0][currentMove];
11676         blackTimeRemaining = timeRemaining[1][currentMove];
11677         DisplayBothClocks();
11678         DisplayMove(currentMove - 1);
11679         ClearHighlights();/*!! could figure this out*/
11680         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11681         SendToProgram("remove\n", &first);
11682         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11683         break;
11684
11685       case BeginningOfGame:
11686       default:
11687         break;
11688
11689       case IcsPlayingWhite:
11690       case IcsPlayingBlack:
11691         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11692             SendToICS(ics_prefix);
11693             SendToICS("takeback 2\n");
11694         } else {
11695             SendToICS(ics_prefix);
11696             SendToICS("takeback 1\n");
11697         }
11698         break;
11699     }
11700 }
11701
11702 void
11703 MoveNowEvent()
11704 {
11705     ChessProgramState *cps;
11706
11707     switch (gameMode) {
11708       case MachinePlaysWhite:
11709         if (!WhiteOnMove(forwardMostMove)) {
11710             DisplayError(_("It is your turn"), 0);
11711             return;
11712         }
11713         cps = &first;
11714         break;
11715       case MachinePlaysBlack:
11716         if (WhiteOnMove(forwardMostMove)) {
11717             DisplayError(_("It is your turn"), 0);
11718             return;
11719         }
11720         cps = &first;
11721         break;
11722       case TwoMachinesPlay:
11723         if (WhiteOnMove(forwardMostMove) ==
11724             (first.twoMachinesColor[0] == 'w')) {
11725             cps = &first;
11726         } else {
11727             cps = &second;
11728         }
11729         break;
11730       case BeginningOfGame:
11731       default:
11732         return;
11733     }
11734     SendToProgram("?\n", cps);
11735 }
11736
11737 void
11738 TruncateGameEvent()
11739 {
11740     EditGameEvent();
11741     if (gameMode != EditGame) return;
11742     TruncateGame();
11743 }
11744
11745 void
11746 TruncateGame()
11747 {
11748     if (forwardMostMove > currentMove) {
11749         if (gameInfo.resultDetails != NULL) {
11750             free(gameInfo.resultDetails);
11751             gameInfo.resultDetails = NULL;
11752             gameInfo.result = GameUnfinished;
11753         }
11754         forwardMostMove = currentMove;
11755         HistorySet(parseList, backwardMostMove, forwardMostMove,
11756                    currentMove-1);
11757     }
11758 }
11759
11760 void
11761 HintEvent()
11762 {
11763     if (appData.noChessProgram) return;
11764     switch (gameMode) {
11765       case MachinePlaysWhite:
11766         if (WhiteOnMove(forwardMostMove)) {
11767             DisplayError(_("Wait until your turn"), 0);
11768             return;
11769         }
11770         break;
11771       case BeginningOfGame:
11772       case MachinePlaysBlack:
11773         if (!WhiteOnMove(forwardMostMove)) {
11774             DisplayError(_("Wait until your turn"), 0);
11775             return;
11776         }
11777         break;
11778       default:
11779         DisplayError(_("No hint available"), 0);
11780         return;
11781     }
11782     SendToProgram("hint\n", &first);
11783     hintRequested = TRUE;
11784 }
11785
11786 void
11787 BookEvent()
11788 {
11789     if (appData.noChessProgram) return;
11790     switch (gameMode) {
11791       case MachinePlaysWhite:
11792         if (WhiteOnMove(forwardMostMove)) {
11793             DisplayError(_("Wait until your turn"), 0);
11794             return;
11795         }
11796         break;
11797       case BeginningOfGame:
11798       case MachinePlaysBlack:
11799         if (!WhiteOnMove(forwardMostMove)) {
11800             DisplayError(_("Wait until your turn"), 0);
11801             return;
11802         }
11803         break;
11804       case EditPosition:
11805         EditPositionDone();
11806         break;
11807       case TwoMachinesPlay:
11808         return;
11809       default:
11810         break;
11811     }
11812     SendToProgram("bk\n", &first);
11813     bookOutput[0] = NULLCHAR;
11814     bookRequested = TRUE;
11815 }
11816
11817 void
11818 AboutGameEvent()
11819 {
11820     char *tags = PGNTags(&gameInfo);
11821     TagsPopUp(tags, CmailMsg());
11822     free(tags);
11823 }
11824
11825 /* end button procedures */
11826
11827 void
11828 PrintPosition(fp, move)
11829      FILE *fp;
11830      int move;
11831 {
11832     int i, j;
11833     
11834     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11835         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11836             char c = PieceToChar(boards[move][i][j]);
11837             fputc(c == 'x' ? '.' : c, fp);
11838             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11839         }
11840     }
11841     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11842       fprintf(fp, "white to play\n");
11843     else
11844       fprintf(fp, "black to play\n");
11845 }
11846
11847 void
11848 PrintOpponents(fp)
11849      FILE *fp;
11850 {
11851     if (gameInfo.white != NULL) {
11852         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11853     } else {
11854         fprintf(fp, "\n");
11855     }
11856 }
11857
11858 /* Find last component of program's own name, using some heuristics */
11859 void
11860 TidyProgramName(prog, host, buf)
11861      char *prog, *host, buf[MSG_SIZ];
11862 {
11863     char *p, *q;
11864     int local = (strcmp(host, "localhost") == 0);
11865     while (!local && (p = strchr(prog, ';')) != NULL) {
11866         p++;
11867         while (*p == ' ') p++;
11868         prog = p;
11869     }
11870     if (*prog == '"' || *prog == '\'') {
11871         q = strchr(prog + 1, *prog);
11872     } else {
11873         q = strchr(prog, ' ');
11874     }
11875     if (q == NULL) q = prog + strlen(prog);
11876     p = q;
11877     while (p >= prog && *p != '/' && *p != '\\') p--;
11878     p++;
11879     if(p == prog && *p == '"') p++;
11880     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11881     memcpy(buf, p, q - p);
11882     buf[q - p] = NULLCHAR;
11883     if (!local) {
11884         strcat(buf, "@");
11885         strcat(buf, host);
11886     }
11887 }
11888
11889 char *
11890 TimeControlTagValue()
11891 {
11892     char buf[MSG_SIZ];
11893     if (!appData.clockMode) {
11894         strcpy(buf, "-");
11895     } else if (movesPerSession > 0) {
11896         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11897     } else if (timeIncrement == 0) {
11898         sprintf(buf, "%ld", timeControl/1000);
11899     } else {
11900         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11901     }
11902     return StrSave(buf);
11903 }
11904
11905 void
11906 SetGameInfo()
11907 {
11908     /* This routine is used only for certain modes */
11909     VariantClass v = gameInfo.variant;
11910     ClearGameInfo(&gameInfo);
11911     gameInfo.variant = v;
11912
11913     switch (gameMode) {
11914       case MachinePlaysWhite:
11915         gameInfo.event = StrSave( appData.pgnEventHeader );
11916         gameInfo.site = StrSave(HostName());
11917         gameInfo.date = PGNDate();
11918         gameInfo.round = StrSave("-");
11919         gameInfo.white = StrSave(first.tidy);
11920         gameInfo.black = StrSave(UserName());
11921         gameInfo.timeControl = TimeControlTagValue();
11922         break;
11923
11924       case MachinePlaysBlack:
11925         gameInfo.event = StrSave( appData.pgnEventHeader );
11926         gameInfo.site = StrSave(HostName());
11927         gameInfo.date = PGNDate();
11928         gameInfo.round = StrSave("-");
11929         gameInfo.white = StrSave(UserName());
11930         gameInfo.black = StrSave(first.tidy);
11931         gameInfo.timeControl = TimeControlTagValue();
11932         break;
11933
11934       case TwoMachinesPlay:
11935         gameInfo.event = StrSave( appData.pgnEventHeader );
11936         gameInfo.site = StrSave(HostName());
11937         gameInfo.date = PGNDate();
11938         if (matchGame > 0) {
11939             char buf[MSG_SIZ];
11940             sprintf(buf, "%d", matchGame);
11941             gameInfo.round = StrSave(buf);
11942         } else {
11943             gameInfo.round = StrSave("-");
11944         }
11945         if (first.twoMachinesColor[0] == 'w') {
11946             gameInfo.white = StrSave(first.tidy);
11947             gameInfo.black = StrSave(second.tidy);
11948         } else {
11949             gameInfo.white = StrSave(second.tidy);
11950             gameInfo.black = StrSave(first.tidy);
11951         }
11952         gameInfo.timeControl = TimeControlTagValue();
11953         break;
11954
11955       case EditGame:
11956         gameInfo.event = StrSave("Edited game");
11957         gameInfo.site = StrSave(HostName());
11958         gameInfo.date = PGNDate();
11959         gameInfo.round = StrSave("-");
11960         gameInfo.white = StrSave("-");
11961         gameInfo.black = StrSave("-");
11962         break;
11963
11964       case EditPosition:
11965         gameInfo.event = StrSave("Edited position");
11966         gameInfo.site = StrSave(HostName());
11967         gameInfo.date = PGNDate();
11968         gameInfo.round = StrSave("-");
11969         gameInfo.white = StrSave("-");
11970         gameInfo.black = StrSave("-");
11971         break;
11972
11973       case IcsPlayingWhite:
11974       case IcsPlayingBlack:
11975       case IcsObserving:
11976       case IcsExamining:
11977         break;
11978
11979       case PlayFromGameFile:
11980         gameInfo.event = StrSave("Game from non-PGN file");
11981         gameInfo.site = StrSave(HostName());
11982         gameInfo.date = PGNDate();
11983         gameInfo.round = StrSave("-");
11984         gameInfo.white = StrSave("?");
11985         gameInfo.black = StrSave("?");
11986         break;
11987
11988       default:
11989         break;
11990     }
11991 }
11992
11993 void
11994 ReplaceComment(index, text)
11995      int index;
11996      char *text;
11997 {
11998     int len;
11999
12000     while (*text == '\n') text++;
12001     len = strlen(text);
12002     while (len > 0 && text[len - 1] == '\n') len--;
12003
12004     if (commentList[index] != NULL)
12005       free(commentList[index]);
12006
12007     if (len == 0) {
12008         commentList[index] = NULL;
12009         return;
12010     }
12011     commentList[index] = (char *) malloc(len + 2);
12012     strncpy(commentList[index], text, len);
12013     commentList[index][len] = '\n';
12014     commentList[index][len + 1] = NULLCHAR;
12015 }
12016
12017 void
12018 CrushCRs(text)
12019      char *text;
12020 {
12021   char *p = text;
12022   char *q = text;
12023   char ch;
12024
12025   do {
12026     ch = *p++;
12027     if (ch == '\r') continue;
12028     *q++ = ch;
12029   } while (ch != '\0');
12030 }
12031
12032 void
12033 AppendComment(index, text)
12034      int index;
12035      char *text;
12036 {
12037     int oldlen, len;
12038     char *old;
12039
12040     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12041
12042     CrushCRs(text);
12043     while (*text == '\n') text++;
12044     len = strlen(text);
12045     while (len > 0 && text[len - 1] == '\n') len--;
12046
12047     if (len == 0) return;
12048
12049     if (commentList[index] != NULL) {
12050         old = commentList[index];
12051         oldlen = strlen(old);
12052         commentList[index] = (char *) malloc(oldlen + len + 2);
12053         strcpy(commentList[index], old);
12054         free(old);
12055         strncpy(&commentList[index][oldlen], text, len);
12056         commentList[index][oldlen + len] = '\n';
12057         commentList[index][oldlen + len + 1] = NULLCHAR;
12058     } else {
12059         commentList[index] = (char *) malloc(len + 2);
12060         strncpy(commentList[index], text, len);
12061         commentList[index][len] = '\n';
12062         commentList[index][len + 1] = NULLCHAR;
12063     }
12064 }
12065
12066 static char * FindStr( char * text, char * sub_text )
12067 {
12068     char * result = strstr( text, sub_text );
12069
12070     if( result != NULL ) {
12071         result += strlen( sub_text );
12072     }
12073
12074     return result;
12075 }
12076
12077 /* [AS] Try to extract PV info from PGN comment */
12078 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12079 char *GetInfoFromComment( int index, char * text )
12080 {
12081     char * sep = text;
12082
12083     if( text != NULL && index > 0 ) {
12084         int score = 0;
12085         int depth = 0;
12086         int time = -1, sec = 0, deci;
12087         char * s_eval = FindStr( text, "[%eval " );
12088         char * s_emt = FindStr( text, "[%emt " );
12089
12090         if( s_eval != NULL || s_emt != NULL ) {
12091             /* New style */
12092             char delim;
12093
12094             if( s_eval != NULL ) {
12095                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12096                     return text;
12097                 }
12098
12099                 if( delim != ']' ) {
12100                     return text;
12101                 }
12102             }
12103
12104             if( s_emt != NULL ) {
12105             }
12106         }
12107         else {
12108             /* We expect something like: [+|-]nnn.nn/dd */
12109             int score_lo = 0;
12110
12111             sep = strchr( text, '/' );
12112             if( sep == NULL || sep < (text+4) ) {
12113                 return text;
12114             }
12115
12116             time = -1; sec = -1; deci = -1;
12117             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12118                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12119                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12120                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12121                 return text;
12122             }
12123
12124             if( score_lo < 0 || score_lo >= 100 ) {
12125                 return text;
12126             }
12127
12128             if(sec >= 0) time = 600*time + 10*sec; else
12129             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12130
12131             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12132
12133             /* [HGM] PV time: now locate end of PV info */
12134             while( *++sep >= '0' && *sep <= '9'); // strip depth
12135             if(time >= 0)
12136             while( *++sep >= '0' && *sep <= '9'); // strip time
12137             if(sec >= 0)
12138             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12139             if(deci >= 0)
12140             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12141             while(*sep == ' ') sep++;
12142         }
12143
12144         if( depth <= 0 ) {
12145             return text;
12146         }
12147
12148         if( time < 0 ) {
12149             time = -1;
12150         }
12151
12152         pvInfoList[index-1].depth = depth;
12153         pvInfoList[index-1].score = score;
12154         pvInfoList[index-1].time  = 10*time; // centi-sec
12155     }
12156     return sep;
12157 }
12158
12159 void
12160 SendToProgram(message, cps)
12161      char *message;
12162      ChessProgramState *cps;
12163 {
12164     int count, outCount, error;
12165     char buf[MSG_SIZ];
12166
12167     if (cps->pr == NULL) return;
12168     Attention(cps);
12169     
12170     if (appData.debugMode) {
12171         TimeMark now;
12172         GetTimeMark(&now);
12173         fprintf(debugFP, "%ld >%-6s: %s", 
12174                 SubtractTimeMarks(&now, &programStartTime),
12175                 cps->which, message);
12176     }
12177     
12178     count = strlen(message);
12179     outCount = OutputToProcess(cps->pr, message, count, &error);
12180     if (outCount < count && !exiting 
12181                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12182         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12183         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12184             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12185                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12186                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12187             } else {
12188                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12189             }
12190             gameInfo.resultDetails = buf;
12191         }
12192         DisplayFatalError(buf, error, 1);
12193     }
12194 }
12195
12196 void
12197 ReceiveFromProgram(isr, closure, message, count, error)
12198      InputSourceRef isr;
12199      VOIDSTAR closure;
12200      char *message;
12201      int count;
12202      int error;
12203 {
12204     char *end_str;
12205     char buf[MSG_SIZ];
12206     ChessProgramState *cps = (ChessProgramState *)closure;
12207
12208     if (isr != cps->isr) return; /* Killed intentionally */
12209     if (count <= 0) {
12210         if (count == 0) {
12211             sprintf(buf,
12212                     _("Error: %s chess program (%s) exited unexpectedly"),
12213                     cps->which, cps->program);
12214         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12215                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12216                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12217                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12218                 } else {
12219                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12220                 }
12221                 gameInfo.resultDetails = buf;
12222             }
12223             RemoveInputSource(cps->isr);
12224             DisplayFatalError(buf, 0, 1);
12225         } else {
12226             sprintf(buf,
12227                     _("Error reading from %s chess program (%s)"),
12228                     cps->which, cps->program);
12229             RemoveInputSource(cps->isr);
12230
12231             /* [AS] Program is misbehaving badly... kill it */
12232             if( count == -2 ) {
12233                 DestroyChildProcess( cps->pr, 9 );
12234                 cps->pr = NoProc;
12235             }
12236
12237             DisplayFatalError(buf, error, 1);
12238         }
12239         return;
12240     }
12241     
12242     if ((end_str = strchr(message, '\r')) != NULL)
12243       *end_str = NULLCHAR;
12244     if ((end_str = strchr(message, '\n')) != NULL)
12245       *end_str = NULLCHAR;
12246     
12247     if (appData.debugMode) {
12248         TimeMark now; int print = 1;
12249         char *quote = ""; char c; int i;
12250
12251         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12252                 char start = message[0];
12253                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12254                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12255                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12256                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12257                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12258                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12259                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
12260                         { quote = "# "; print = (appData.engineComments == 2); }
12261                 message[0] = start; // restore original message
12262         }
12263         if(print) {
12264                 GetTimeMark(&now);
12265                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12266                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12267                         quote,
12268                         message);
12269         }
12270     }
12271
12272     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12273     if (appData.icsEngineAnalyze) {
12274         if (strstr(message, "whisper") != NULL ||
12275              strstr(message, "kibitz") != NULL || 
12276             strstr(message, "tellics") != NULL) return;
12277     }
12278
12279     HandleMachineMove(message, cps);
12280 }
12281
12282
12283 void
12284 SendTimeControl(cps, mps, tc, inc, sd, st)
12285      ChessProgramState *cps;
12286      int mps, inc, sd, st;
12287      long tc;
12288 {
12289     char buf[MSG_SIZ];
12290     int seconds;
12291
12292     if( timeControl_2 > 0 ) {
12293         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12294             tc = timeControl_2;
12295         }
12296     }
12297     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12298     inc /= cps->timeOdds;
12299     st  /= cps->timeOdds;
12300
12301     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12302
12303     if (st > 0) {
12304       /* Set exact time per move, normally using st command */
12305       if (cps->stKludge) {
12306         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12307         seconds = st % 60;
12308         if (seconds == 0) {
12309           sprintf(buf, "level 1 %d\n", st/60);
12310         } else {
12311           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12312         }
12313       } else {
12314         sprintf(buf, "st %d\n", st);
12315       }
12316     } else {
12317       /* Set conventional or incremental time control, using level command */
12318       if (seconds == 0) {
12319         /* Note old gnuchess bug -- minutes:seconds used to not work.
12320            Fixed in later versions, but still avoid :seconds
12321            when seconds is 0. */
12322         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12323       } else {
12324         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12325                 seconds, inc/1000);
12326       }
12327     }
12328     SendToProgram(buf, cps);
12329
12330     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12331     /* Orthogonally, limit search to given depth */
12332     if (sd > 0) {
12333       if (cps->sdKludge) {
12334         sprintf(buf, "depth\n%d\n", sd);
12335       } else {
12336         sprintf(buf, "sd %d\n", sd);
12337       }
12338       SendToProgram(buf, cps);
12339     }
12340
12341     if(cps->nps > 0) { /* [HGM] nps */
12342         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12343         else {
12344                 sprintf(buf, "nps %d\n", cps->nps);
12345               SendToProgram(buf, cps);
12346         }
12347     }
12348 }
12349
12350 ChessProgramState *WhitePlayer()
12351 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12352 {
12353     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12354        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12355         return &second;
12356     return &first;
12357 }
12358
12359 void
12360 SendTimeRemaining(cps, machineWhite)
12361      ChessProgramState *cps;
12362      int /*boolean*/ machineWhite;
12363 {
12364     char message[MSG_SIZ];
12365     long time, otime;
12366
12367     /* Note: this routine must be called when the clocks are stopped
12368        or when they have *just* been set or switched; otherwise
12369        it will be off by the time since the current tick started.
12370     */
12371     if (machineWhite) {
12372         time = whiteTimeRemaining / 10;
12373         otime = blackTimeRemaining / 10;
12374     } else {
12375         time = blackTimeRemaining / 10;
12376         otime = whiteTimeRemaining / 10;
12377     }
12378     /* [HGM] translate opponent's time by time-odds factor */
12379     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12380     if (appData.debugMode) {
12381         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12382     }
12383
12384     if (time <= 0) time = 1;
12385     if (otime <= 0) otime = 1;
12386     
12387     sprintf(message, "time %ld\n", time);
12388     SendToProgram(message, cps);
12389
12390     sprintf(message, "otim %ld\n", otime);
12391     SendToProgram(message, cps);
12392 }
12393
12394 int
12395 BoolFeature(p, name, loc, cps)
12396      char **p;
12397      char *name;
12398      int *loc;
12399      ChessProgramState *cps;
12400 {
12401   char buf[MSG_SIZ];
12402   int len = strlen(name);
12403   int val;
12404   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12405     (*p) += len + 1;
12406     sscanf(*p, "%d", &val);
12407     *loc = (val != 0);
12408     while (**p && **p != ' ') (*p)++;
12409     sprintf(buf, "accepted %s\n", name);
12410     SendToProgram(buf, cps);
12411     return TRUE;
12412   }
12413   return FALSE;
12414 }
12415
12416 int
12417 IntFeature(p, name, loc, cps)
12418      char **p;
12419      char *name;
12420      int *loc;
12421      ChessProgramState *cps;
12422 {
12423   char buf[MSG_SIZ];
12424   int len = strlen(name);
12425   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12426     (*p) += len + 1;
12427     sscanf(*p, "%d", loc);
12428     while (**p && **p != ' ') (*p)++;
12429     sprintf(buf, "accepted %s\n", name);
12430     SendToProgram(buf, cps);
12431     return TRUE;
12432   }
12433   return FALSE;
12434 }
12435
12436 int
12437 StringFeature(p, name, loc, cps)
12438      char **p;
12439      char *name;
12440      char loc[];
12441      ChessProgramState *cps;
12442 {
12443   char buf[MSG_SIZ];
12444   int len = strlen(name);
12445   if (strncmp((*p), name, len) == 0
12446       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12447     (*p) += len + 2;
12448     sscanf(*p, "%[^\"]", loc);
12449     while (**p && **p != '\"') (*p)++;
12450     if (**p == '\"') (*p)++;
12451     sprintf(buf, "accepted %s\n", name);
12452     SendToProgram(buf, cps);
12453     return TRUE;
12454   }
12455   return FALSE;
12456 }
12457
12458 int 
12459 ParseOption(Option *opt, ChessProgramState *cps)
12460 // [HGM] options: process the string that defines an engine option, and determine
12461 // name, type, default value, and allowed value range
12462 {
12463         char *p, *q, buf[MSG_SIZ];
12464         int n, min = (-1)<<31, max = 1<<31, def;
12465
12466         if(p = strstr(opt->name, " -spin ")) {
12467             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12468             if(max < min) max = min; // enforce consistency
12469             if(def < min) def = min;
12470             if(def > max) def = max;
12471             opt->value = def;
12472             opt->min = min;
12473             opt->max = max;
12474             opt->type = Spin;
12475         } else if((p = strstr(opt->name, " -slider "))) {
12476             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12477             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12478             if(max < min) max = min; // enforce consistency
12479             if(def < min) def = min;
12480             if(def > max) def = max;
12481             opt->value = def;
12482             opt->min = min;
12483             opt->max = max;
12484             opt->type = Spin; // Slider;
12485         } else if((p = strstr(opt->name, " -string "))) {
12486             opt->textValue = p+9;
12487             opt->type = TextBox;
12488         } else if((p = strstr(opt->name, " -file "))) {
12489             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12490             opt->textValue = p+7;
12491             opt->type = TextBox; // FileName;
12492         } else if((p = strstr(opt->name, " -path "))) {
12493             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12494             opt->textValue = p+7;
12495             opt->type = TextBox; // PathName;
12496         } else if(p = strstr(opt->name, " -check ")) {
12497             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12498             opt->value = (def != 0);
12499             opt->type = CheckBox;
12500         } else if(p = strstr(opt->name, " -combo ")) {
12501             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12502             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12503             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12504             opt->value = n = 0;
12505             while(q = StrStr(q, " /// ")) {
12506                 n++; *q = 0;    // count choices, and null-terminate each of them
12507                 q += 5;
12508                 if(*q == '*') { // remember default, which is marked with * prefix
12509                     q++;
12510                     opt->value = n;
12511                 }
12512                 cps->comboList[cps->comboCnt++] = q;
12513             }
12514             cps->comboList[cps->comboCnt++] = NULL;
12515             opt->max = n + 1;
12516             opt->type = ComboBox;
12517         } else if(p = strstr(opt->name, " -button")) {
12518             opt->type = Button;
12519         } else if(p = strstr(opt->name, " -save")) {
12520             opt->type = SaveButton;
12521         } else return FALSE;
12522         *p = 0; // terminate option name
12523         // now look if the command-line options define a setting for this engine option.
12524         if(cps->optionSettings && cps->optionSettings[0])
12525             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12526         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12527                 sprintf(buf, "option %s", p);
12528                 if(p = strstr(buf, ",")) *p = 0;
12529                 strcat(buf, "\n");
12530                 SendToProgram(buf, cps);
12531         }
12532         return TRUE;
12533 }
12534
12535 void
12536 FeatureDone(cps, val)
12537      ChessProgramState* cps;
12538      int val;
12539 {
12540   DelayedEventCallback cb = GetDelayedEvent();
12541   if ((cb == InitBackEnd3 && cps == &first) ||
12542       (cb == TwoMachinesEventIfReady && cps == &second)) {
12543     CancelDelayedEvent();
12544     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12545   }
12546   cps->initDone = val;
12547 }
12548
12549 /* Parse feature command from engine */
12550 void
12551 ParseFeatures(args, cps)
12552      char* args;
12553      ChessProgramState *cps;  
12554 {
12555   char *p = args;
12556   char *q;
12557   int val;
12558   char buf[MSG_SIZ];
12559
12560   for (;;) {
12561     while (*p == ' ') p++;
12562     if (*p == NULLCHAR) return;
12563
12564     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12565     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
12566     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
12567     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
12568     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
12569     if (BoolFeature(&p, "reuse", &val, cps)) {
12570       /* Engine can disable reuse, but can't enable it if user said no */
12571       if (!val) cps->reuse = FALSE;
12572       continue;
12573     }
12574     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12575     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12576       if (gameMode == TwoMachinesPlay) {
12577         DisplayTwoMachinesTitle();
12578       } else {
12579         DisplayTitle("");
12580       }
12581       continue;
12582     }
12583     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12584     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12585     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12586     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12587     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12588     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12589     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12590     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12591     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12592     if (IntFeature(&p, "done", &val, cps)) {
12593       FeatureDone(cps, val);
12594       continue;
12595     }
12596     /* Added by Tord: */
12597     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12598     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12599     /* End of additions by Tord */
12600
12601     /* [HGM] added features: */
12602     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12603     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12604     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12605     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12606     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12607     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12608     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12609         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12610             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12611             SendToProgram(buf, cps);
12612             continue;
12613         }
12614         if(cps->nrOptions >= MAX_OPTIONS) {
12615             cps->nrOptions--;
12616             sprintf(buf, "%s engine has too many options\n", cps->which);
12617             DisplayError(buf, 0);
12618         }
12619         continue;
12620     }
12621     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12622     /* End of additions by HGM */
12623
12624     /* unknown feature: complain and skip */
12625     q = p;
12626     while (*q && *q != '=') q++;
12627     sprintf(buf, "rejected %.*s\n", q-p, p);
12628     SendToProgram(buf, cps);
12629     p = q;
12630     if (*p == '=') {
12631       p++;
12632       if (*p == '\"') {
12633         p++;
12634         while (*p && *p != '\"') p++;
12635         if (*p == '\"') p++;
12636       } else {
12637         while (*p && *p != ' ') p++;
12638       }
12639     }
12640   }
12641
12642 }
12643
12644 void
12645 PeriodicUpdatesEvent(newState)
12646      int newState;
12647 {
12648     if (newState == appData.periodicUpdates)
12649       return;
12650
12651     appData.periodicUpdates=newState;
12652
12653     /* Display type changes, so update it now */
12654     DisplayAnalysis();
12655
12656     /* Get the ball rolling again... */
12657     if (newState) {
12658         AnalysisPeriodicEvent(1);
12659         StartAnalysisClock();
12660     }
12661 }
12662
12663 void
12664 PonderNextMoveEvent(newState)
12665      int newState;
12666 {
12667     if (newState == appData.ponderNextMove) return;
12668     if (gameMode == EditPosition) EditPositionDone();
12669     if (newState) {
12670         SendToProgram("hard\n", &first);
12671         if (gameMode == TwoMachinesPlay) {
12672             SendToProgram("hard\n", &second);
12673         }
12674     } else {
12675         SendToProgram("easy\n", &first);
12676         thinkOutput[0] = NULLCHAR;
12677         if (gameMode == TwoMachinesPlay) {
12678             SendToProgram("easy\n", &second);
12679         }
12680     }
12681     appData.ponderNextMove = newState;
12682 }
12683
12684 void
12685 NewSettingEvent(option, command, value)
12686      char *command;
12687      int option, value;
12688 {
12689     char buf[MSG_SIZ];
12690
12691     if (gameMode == EditPosition) EditPositionDone();
12692     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12693     SendToProgram(buf, &first);
12694     if (gameMode == TwoMachinesPlay) {
12695         SendToProgram(buf, &second);
12696     }
12697 }
12698
12699 void
12700 ShowThinkingEvent()
12701 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12702 {
12703     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12704     int newState = appData.showThinking
12705         // [HGM] thinking: other features now need thinking output as well
12706         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12707     
12708     if (oldState == newState) return;
12709     oldState = newState;
12710     if (gameMode == EditPosition) EditPositionDone();
12711     if (oldState) {
12712         SendToProgram("post\n", &first);
12713         if (gameMode == TwoMachinesPlay) {
12714             SendToProgram("post\n", &second);
12715         }
12716     } else {
12717         SendToProgram("nopost\n", &first);
12718         thinkOutput[0] = NULLCHAR;
12719         if (gameMode == TwoMachinesPlay) {
12720             SendToProgram("nopost\n", &second);
12721         }
12722     }
12723 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12724 }
12725
12726 void
12727 AskQuestionEvent(title, question, replyPrefix, which)
12728      char *title; char *question; char *replyPrefix; char *which;
12729 {
12730   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12731   if (pr == NoProc) return;
12732   AskQuestion(title, question, replyPrefix, pr);
12733 }
12734
12735 void
12736 DisplayMove(moveNumber)
12737      int moveNumber;
12738 {
12739     char message[MSG_SIZ];
12740     char res[MSG_SIZ];
12741     char cpThinkOutput[MSG_SIZ];
12742
12743     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12744     
12745     if (moveNumber == forwardMostMove - 1 || 
12746         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12747
12748         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12749
12750         if (strchr(cpThinkOutput, '\n')) {
12751             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12752         }
12753     } else {
12754         *cpThinkOutput = NULLCHAR;
12755     }
12756
12757     /* [AS] Hide thinking from human user */
12758     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12759         *cpThinkOutput = NULLCHAR;
12760         if( thinkOutput[0] != NULLCHAR ) {
12761             int i;
12762
12763             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12764                 cpThinkOutput[i] = '.';
12765             }
12766             cpThinkOutput[i] = NULLCHAR;
12767             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12768         }
12769     }
12770
12771     if (moveNumber == forwardMostMove - 1 &&
12772         gameInfo.resultDetails != NULL) {
12773         if (gameInfo.resultDetails[0] == NULLCHAR) {
12774             sprintf(res, " %s", PGNResult(gameInfo.result));
12775         } else {
12776             sprintf(res, " {%s} %s",
12777                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12778         }
12779     } else {
12780         res[0] = NULLCHAR;
12781     }
12782
12783     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12784         DisplayMessage(res, cpThinkOutput);
12785     } else {
12786         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12787                 WhiteOnMove(moveNumber) ? " " : ".. ",
12788                 parseList[moveNumber], res);
12789         DisplayMessage(message, cpThinkOutput);
12790     }
12791 }
12792
12793 void
12794 DisplayAnalysisText(text)
12795      char *text;
12796 {
12797     char buf[MSG_SIZ];
12798
12799     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile 
12800                || appData.icsEngineAnalyze) {
12801         sprintf(buf, "Analysis (%s)", first.tidy);
12802         AnalysisPopUp(buf, text);
12803     }
12804 }
12805
12806 static int
12807 only_one_move(str)
12808      char *str;
12809 {
12810     while (*str && isspace(*str)) ++str;
12811     while (*str && !isspace(*str)) ++str;
12812     if (!*str) return 1;
12813     while (*str && isspace(*str)) ++str;
12814     if (!*str) return 1;
12815     return 0;
12816 }
12817
12818 void
12819 DisplayAnalysis()
12820 {
12821     char buf[MSG_SIZ];
12822     char lst[MSG_SIZ / 2];
12823     double nps;
12824     static char *xtra[] = { "", " (--)", " (++)" };
12825     int h, m, s, cs;
12826   
12827     if (programStats.time == 0) {
12828         programStats.time = 1;
12829     }
12830   
12831     if (programStats.got_only_move) {
12832         safeStrCpy(buf, programStats.movelist, sizeof(buf));
12833     } else {
12834         safeStrCpy( lst, programStats.movelist, sizeof(lst));
12835
12836         nps = (u64ToDouble(programStats.nodes) /
12837              ((double)programStats.time /100.0));
12838
12839         cs = programStats.time % 100;
12840         s = programStats.time / 100;
12841         h = (s / (60*60));
12842         s = s - h*60*60;
12843         m = (s/60);
12844         s = s - m*60;
12845
12846         if (programStats.moves_left > 0 && appData.periodicUpdates) {
12847           if (programStats.move_name[0] != NULLCHAR) {
12848             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12849                     programStats.depth,
12850                     programStats.nr_moves-programStats.moves_left,
12851                     programStats.nr_moves, programStats.move_name,
12852                     ((float)programStats.score)/100.0, lst,
12853                     only_one_move(lst)?
12854                     xtra[programStats.got_fail] : "",
12855                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12856           } else {
12857             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12858                     programStats.depth,
12859                     programStats.nr_moves-programStats.moves_left,
12860                     programStats.nr_moves, ((float)programStats.score)/100.0,
12861                     lst,
12862                     only_one_move(lst)?
12863                     xtra[programStats.got_fail] : "",
12864                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12865           }
12866         } else {
12867             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12868                     programStats.depth,
12869                     ((float)programStats.score)/100.0,
12870                     lst,
12871                     only_one_move(lst)?
12872                     xtra[programStats.got_fail] : "",
12873                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12874         }
12875     }
12876     DisplayAnalysisText(buf);
12877 }
12878
12879 void
12880 DisplayComment(moveNumber, text)
12881      int moveNumber;
12882      char *text;
12883 {
12884     char title[MSG_SIZ];
12885     char buf[8000]; // comment can be long!
12886     int score, depth;
12887
12888     if( appData.autoDisplayComment ) {
12889         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12890             strcpy(title, "Comment");
12891         } else {
12892             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12893                     WhiteOnMove(moveNumber) ? " " : ".. ",
12894                     parseList[moveNumber]);
12895         }
12896         // [HGM] PV info: display PV info together with (or as) comment
12897         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12898             if(text == NULL) text = "";                                           
12899             score = pvInfoList[moveNumber].score;
12900             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12901                               depth, (pvInfoList[moveNumber].time+50)/100, text);
12902             text = buf;
12903         }
12904     } else title[0] = 0;
12905
12906     if (text != NULL)
12907         CommentPopUp(title, text);
12908 }
12909
12910 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12911  * might be busy thinking or pondering.  It can be omitted if your
12912  * gnuchess is configured to stop thinking immediately on any user
12913  * input.  However, that gnuchess feature depends on the FIONREAD
12914  * ioctl, which does not work properly on some flavors of Unix.
12915  */
12916 void
12917 Attention(cps)
12918      ChessProgramState *cps;
12919 {
12920 #if ATTENTION
12921     if (!cps->useSigint) return;
12922     if (appData.noChessProgram || (cps->pr == NoProc)) return;
12923     switch (gameMode) {
12924       case MachinePlaysWhite:
12925       case MachinePlaysBlack:
12926       case TwoMachinesPlay:
12927       case IcsPlayingWhite:
12928       case IcsPlayingBlack:
12929       case AnalyzeMode:
12930       case AnalyzeFile:
12931         /* Skip if we know it isn't thinking */
12932         if (!cps->maybeThinking) return;
12933         if (appData.debugMode)
12934           fprintf(debugFP, "Interrupting %s\n", cps->which);
12935         InterruptChildProcess(cps->pr);
12936         cps->maybeThinking = FALSE;
12937         break;
12938       default:
12939         break;
12940     }
12941 #endif /*ATTENTION*/
12942 }
12943
12944 int
12945 CheckFlags()
12946 {
12947     if (whiteTimeRemaining <= 0) {
12948         if (!whiteFlag) {
12949             whiteFlag = TRUE;
12950             if (appData.icsActive) {
12951                 if (appData.autoCallFlag &&
12952                     gameMode == IcsPlayingBlack && !blackFlag) {
12953                   SendToICS(ics_prefix);
12954                   SendToICS("flag\n");
12955                 }
12956             } else {
12957                 if (blackFlag) {
12958                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12959                 } else {
12960                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12961                     if (appData.autoCallFlag) {
12962                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12963                         return TRUE;
12964                     }
12965                 }
12966             }
12967         }
12968     }
12969     if (blackTimeRemaining <= 0) {
12970         if (!blackFlag) {
12971             blackFlag = TRUE;
12972             if (appData.icsActive) {
12973                 if (appData.autoCallFlag &&
12974                     gameMode == IcsPlayingWhite && !whiteFlag) {
12975                   SendToICS(ics_prefix);
12976                   SendToICS("flag\n");
12977                 }
12978             } else {
12979                 if (whiteFlag) {
12980                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12981                 } else {
12982                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12983                     if (appData.autoCallFlag) {
12984                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12985                         return TRUE;
12986                     }
12987                 }
12988             }
12989         }
12990     }
12991     return FALSE;
12992 }
12993
12994 void
12995 CheckTimeControl()
12996 {
12997     if (!appData.clockMode || appData.icsActive ||
12998         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12999
13000     /*
13001      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13002      */
13003     if ( !WhiteOnMove(forwardMostMove) )
13004         /* White made time control */
13005         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13006         /* [HGM] time odds: correct new time quota for time odds! */
13007                                             / WhitePlayer()->timeOdds;
13008       else
13009         /* Black made time control */
13010         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13011                                             / WhitePlayer()->other->timeOdds;
13012 }
13013
13014 void
13015 DisplayBothClocks()
13016 {
13017     int wom = gameMode == EditPosition ?
13018       !blackPlaysFirst : WhiteOnMove(currentMove);
13019     DisplayWhiteClock(whiteTimeRemaining, wom);
13020     DisplayBlackClock(blackTimeRemaining, !wom);
13021 }
13022
13023
13024 /* Timekeeping seems to be a portability nightmare.  I think everyone
13025    has ftime(), but I'm really not sure, so I'm including some ifdefs
13026    to use other calls if you don't.  Clocks will be less accurate if
13027    you have neither ftime nor gettimeofday.
13028 */
13029
13030 /* VS 2008 requires the #include outside of the function */
13031 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13032 #include <sys/timeb.h>
13033 #endif
13034
13035 /* Get the current time as a TimeMark */
13036 void
13037 GetTimeMark(tm)
13038      TimeMark *tm;
13039 {
13040 #if HAVE_GETTIMEOFDAY
13041
13042     struct timeval timeVal;
13043     struct timezone timeZone;
13044
13045     gettimeofday(&timeVal, &timeZone);
13046     tm->sec = (long) timeVal.tv_sec; 
13047     tm->ms = (int) (timeVal.tv_usec / 1000L);
13048
13049 #else /*!HAVE_GETTIMEOFDAY*/
13050 #if HAVE_FTIME
13051
13052 // include <sys/timeb.h> / moved to just above start of function
13053     struct timeb timeB;
13054
13055     ftime(&timeB);
13056     tm->sec = (long) timeB.time;
13057     tm->ms = (int) timeB.millitm;
13058
13059 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13060     tm->sec = (long) time(NULL);
13061     tm->ms = 0;
13062 #endif
13063 #endif
13064 }
13065
13066 /* Return the difference in milliseconds between two
13067    time marks.  We assume the difference will fit in a long!
13068 */
13069 long
13070 SubtractTimeMarks(tm2, tm1)
13071      TimeMark *tm2, *tm1;
13072 {
13073     return 1000L*(tm2->sec - tm1->sec) +
13074            (long) (tm2->ms - tm1->ms);
13075 }
13076
13077
13078 /*
13079  * Code to manage the game clocks.
13080  *
13081  * In tournament play, black starts the clock and then white makes a move.
13082  * We give the human user a slight advantage if he is playing white---the
13083  * clocks don't run until he makes his first move, so it takes zero time.
13084  * Also, we don't account for network lag, so we could get out of sync
13085  * with GNU Chess's clock -- but then, referees are always right.  
13086  */
13087
13088 static TimeMark tickStartTM;
13089 static long intendedTickLength;
13090
13091 long
13092 NextTickLength(timeRemaining)
13093      long timeRemaining;
13094 {
13095     long nominalTickLength, nextTickLength;
13096
13097     if (timeRemaining > 0L && timeRemaining <= 10000L)
13098       nominalTickLength = 100L;
13099     else
13100       nominalTickLength = 1000L;
13101     nextTickLength = timeRemaining % nominalTickLength;
13102     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13103
13104     return nextTickLength;
13105 }
13106
13107 /* Adjust clock one minute up or down */
13108 void
13109 AdjustClock(Boolean which, int dir)
13110 {
13111     if(which) blackTimeRemaining += 60000*dir;
13112     else      whiteTimeRemaining += 60000*dir;
13113     DisplayBothClocks();
13114 }
13115
13116 /* Stop clocks and reset to a fresh time control */
13117 void
13118 ResetClocks() 
13119 {
13120     (void) StopClockTimer();
13121     if (appData.icsActive) {
13122         whiteTimeRemaining = blackTimeRemaining = 0;
13123     } else { /* [HGM] correct new time quote for time odds */
13124         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13125         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13126     }
13127     if (whiteFlag || blackFlag) {
13128         DisplayTitle("");
13129         whiteFlag = blackFlag = FALSE;
13130     }
13131     DisplayBothClocks();
13132 }
13133
13134 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13135
13136 /* Decrement running clock by amount of time that has passed */
13137 void
13138 DecrementClocks()
13139 {
13140     long timeRemaining;
13141     long lastTickLength, fudge;
13142     TimeMark now;
13143
13144     if (!appData.clockMode) return;
13145     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13146         
13147     GetTimeMark(&now);
13148
13149     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13150
13151     /* Fudge if we woke up a little too soon */
13152     fudge = intendedTickLength - lastTickLength;
13153     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13154
13155     if (WhiteOnMove(forwardMostMove)) {
13156         if(whiteNPS >= 0) lastTickLength = 0;
13157         timeRemaining = whiteTimeRemaining -= lastTickLength;
13158         DisplayWhiteClock(whiteTimeRemaining - fudge,
13159                           WhiteOnMove(currentMove));
13160     } else {
13161         if(blackNPS >= 0) lastTickLength = 0;
13162         timeRemaining = blackTimeRemaining -= lastTickLength;
13163         DisplayBlackClock(blackTimeRemaining - fudge,
13164                           !WhiteOnMove(currentMove));
13165     }
13166
13167     if (CheckFlags()) return;
13168         
13169     tickStartTM = now;
13170     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13171     StartClockTimer(intendedTickLength);
13172
13173     /* if the time remaining has fallen below the alarm threshold, sound the
13174      * alarm. if the alarm has sounded and (due to a takeback or time control
13175      * with increment) the time remaining has increased to a level above the
13176      * threshold, reset the alarm so it can sound again. 
13177      */
13178     
13179     if (appData.icsActive && appData.icsAlarm) {
13180
13181         /* make sure we are dealing with the user's clock */
13182         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13183                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13184            )) return;
13185
13186         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13187             alarmSounded = FALSE;
13188         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13189             PlayAlarmSound();
13190             alarmSounded = TRUE;
13191         }
13192     }
13193 }
13194
13195
13196 /* A player has just moved, so stop the previously running
13197    clock and (if in clock mode) start the other one.
13198    We redisplay both clocks in case we're in ICS mode, because
13199    ICS gives us an update to both clocks after every move.
13200    Note that this routine is called *after* forwardMostMove
13201    is updated, so the last fractional tick must be subtracted
13202    from the color that is *not* on move now.
13203 */
13204 void
13205 SwitchClocks()
13206 {
13207     long lastTickLength;
13208     TimeMark now;
13209     int flagged = FALSE;
13210
13211     GetTimeMark(&now);
13212
13213     if (StopClockTimer() && appData.clockMode) {
13214         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13215         if (WhiteOnMove(forwardMostMove)) {
13216             if(blackNPS >= 0) lastTickLength = 0;
13217             blackTimeRemaining -= lastTickLength;
13218            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13219 //         if(pvInfoList[forwardMostMove-1].time == -1)
13220                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13221                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13222         } else {
13223            if(whiteNPS >= 0) lastTickLength = 0;
13224            whiteTimeRemaining -= lastTickLength;
13225            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13226 //         if(pvInfoList[forwardMostMove-1].time == -1)
13227                  pvInfoList[forwardMostMove-1].time = 
13228                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13229         }
13230         flagged = CheckFlags();
13231     }
13232     CheckTimeControl();
13233
13234     if (flagged || !appData.clockMode) return;
13235
13236     switch (gameMode) {
13237       case MachinePlaysBlack:
13238       case MachinePlaysWhite:
13239       case BeginningOfGame:
13240         if (pausing) return;
13241         break;
13242
13243       case EditGame:
13244       case PlayFromGameFile:
13245       case IcsExamining:
13246         return;
13247
13248       default:
13249         break;
13250     }
13251
13252     tickStartTM = now;
13253     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13254       whiteTimeRemaining : blackTimeRemaining);
13255     StartClockTimer(intendedTickLength);
13256 }
13257         
13258
13259 /* Stop both clocks */
13260 void
13261 StopClocks()
13262 {       
13263     long lastTickLength;
13264     TimeMark now;
13265
13266     if (!StopClockTimer()) return;
13267     if (!appData.clockMode) return;
13268
13269     GetTimeMark(&now);
13270
13271     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13272     if (WhiteOnMove(forwardMostMove)) {
13273         if(whiteNPS >= 0) lastTickLength = 0;
13274         whiteTimeRemaining -= lastTickLength;
13275         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13276     } else {
13277         if(blackNPS >= 0) lastTickLength = 0;
13278         blackTimeRemaining -= lastTickLength;
13279         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13280     }
13281     CheckFlags();
13282 }
13283         
13284 /* Start clock of player on move.  Time may have been reset, so
13285    if clock is already running, stop and restart it. */
13286 void
13287 StartClocks()
13288 {
13289     (void) StopClockTimer(); /* in case it was running already */
13290     DisplayBothClocks();
13291     if (CheckFlags()) return;
13292
13293     if (!appData.clockMode) return;
13294     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13295
13296     GetTimeMark(&tickStartTM);
13297     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13298       whiteTimeRemaining : blackTimeRemaining);
13299
13300    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13301     whiteNPS = blackNPS = -1; 
13302     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13303        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13304         whiteNPS = first.nps;
13305     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13306        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13307         blackNPS = first.nps;
13308     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13309         whiteNPS = second.nps;
13310     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13311         blackNPS = second.nps;
13312     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13313
13314     StartClockTimer(intendedTickLength);
13315 }
13316
13317 char *
13318 TimeString(ms)
13319      long ms;
13320 {
13321     long second, minute, hour, day;
13322     char *sign = "";
13323     static char buf[32];
13324     
13325     if (ms > 0 && ms <= 9900) {
13326       /* convert milliseconds to tenths, rounding up */
13327       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13328
13329       sprintf(buf, " %03.1f ", tenths/10.0);
13330       return buf;
13331     }
13332
13333     /* convert milliseconds to seconds, rounding up */
13334     /* use floating point to avoid strangeness of integer division
13335        with negative dividends on many machines */
13336     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13337
13338     if (second < 0) {
13339         sign = "-";
13340         second = -second;
13341     }
13342     
13343     day = second / (60 * 60 * 24);
13344     second = second % (60 * 60 * 24);
13345     hour = second / (60 * 60);
13346     second = second % (60 * 60);
13347     minute = second / 60;
13348     second = second % 60;
13349     
13350     if (day > 0)
13351       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13352               sign, day, hour, minute, second);
13353     else if (hour > 0)
13354       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13355     else
13356       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13357     
13358     return buf;
13359 }
13360
13361
13362 /*
13363  * This is necessary because some C libraries aren't ANSI C compliant yet.
13364  */
13365 char *
13366 StrStr(string, match)
13367      char *string, *match;
13368 {
13369     int i, length;
13370     
13371     length = strlen(match);
13372     
13373     for (i = strlen(string) - length; i >= 0; i--, string++)
13374       if (!strncmp(match, string, length))
13375         return string;
13376     
13377     return NULL;
13378 }
13379
13380 char *
13381 StrCaseStr(string, match)
13382      char *string, *match;
13383 {
13384     int i, j, length;
13385     
13386     length = strlen(match);
13387     
13388     for (i = strlen(string) - length; i >= 0; i--, string++) {
13389         for (j = 0; j < length; j++) {
13390             if (ToLower(match[j]) != ToLower(string[j]))
13391               break;
13392         }
13393         if (j == length) return string;
13394     }
13395
13396     return NULL;
13397 }
13398
13399 #ifndef _amigados
13400 int
13401 StrCaseCmp(s1, s2)
13402      char *s1, *s2;
13403 {
13404     char c1, c2;
13405     
13406     for (;;) {
13407         c1 = ToLower(*s1++);
13408         c2 = ToLower(*s2++);
13409         if (c1 > c2) return 1;
13410         if (c1 < c2) return -1;
13411         if (c1 == NULLCHAR) return 0;
13412     }
13413 }
13414
13415
13416 int
13417 ToLower(c)
13418      int c;
13419 {
13420     return isupper(c) ? tolower(c) : c;
13421 }
13422
13423
13424 int
13425 ToUpper(c)
13426      int c;
13427 {
13428     return islower(c) ? toupper(c) : c;
13429 }
13430 #endif /* !_amigados    */
13431
13432 char *
13433 StrSave(s)
13434      char *s;
13435 {
13436     char *ret;
13437
13438     if ((ret = (char *) malloc(strlen(s) + 1))) {
13439         strcpy(ret, s);
13440     }
13441     return ret;
13442 }
13443
13444 char *
13445 StrSavePtr(s, savePtr)
13446      char *s, **savePtr;
13447 {
13448     if (*savePtr) {
13449         free(*savePtr);
13450     }
13451     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13452         strcpy(*savePtr, s);
13453     }
13454     return(*savePtr);
13455 }
13456
13457 char *
13458 PGNDate()
13459 {
13460     time_t clock;
13461     struct tm *tm;
13462     char buf[MSG_SIZ];
13463
13464     clock = time((time_t *)NULL);
13465     tm = localtime(&clock);
13466     sprintf(buf, "%04d.%02d.%02d",
13467             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13468     return StrSave(buf);
13469 }
13470
13471
13472 char *
13473 PositionToFEN(move, overrideCastling)
13474      int move;
13475      char *overrideCastling;
13476 {
13477     int i, j, fromX, fromY, toX, toY;
13478     int whiteToPlay;
13479     char buf[128];
13480     char *p, *q;
13481     int emptycount;
13482     ChessSquare piece;
13483
13484     whiteToPlay = (gameMode == EditPosition) ?
13485       !blackPlaysFirst : (move % 2 == 0);
13486     p = buf;
13487
13488     /* Piece placement data */
13489     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13490         emptycount = 0;
13491         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13492             if (boards[move][i][j] == EmptySquare) {
13493                 emptycount++;
13494             } else { ChessSquare piece = boards[move][i][j];
13495                 if (emptycount > 0) {
13496                     if(emptycount<10) /* [HGM] can be >= 10 */
13497                         *p++ = '0' + emptycount;
13498                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13499                     emptycount = 0;
13500                 }
13501                 if(PieceToChar(piece) == '+') {
13502                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13503                     *p++ = '+';
13504                     piece = (ChessSquare)(DEMOTED piece);
13505                 } 
13506                 *p++ = PieceToChar(piece);
13507                 if(p[-1] == '~') {
13508                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13509                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13510                     *p++ = '~';
13511                 }
13512             }
13513         }
13514         if (emptycount > 0) {
13515             if(emptycount<10) /* [HGM] can be >= 10 */
13516                 *p++ = '0' + emptycount;
13517             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13518             emptycount = 0;
13519         }
13520         *p++ = '/';
13521     }
13522     *(p - 1) = ' ';
13523
13524     /* [HGM] print Crazyhouse or Shogi holdings */
13525     if( gameInfo.holdingsWidth ) {
13526         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13527         q = p;
13528         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13529             piece = boards[move][i][BOARD_WIDTH-1];
13530             if( piece != EmptySquare )
13531               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13532                   *p++ = PieceToChar(piece);
13533         }
13534         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13535             piece = boards[move][BOARD_HEIGHT-i-1][0];
13536             if( piece != EmptySquare )
13537               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13538                   *p++ = PieceToChar(piece);
13539         }
13540
13541         if( q == p ) *p++ = '-';
13542         *p++ = ']';
13543         *p++ = ' ';
13544     }
13545
13546     /* Active color */
13547     *p++ = whiteToPlay ? 'w' : 'b';
13548     *p++ = ' ';
13549
13550   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13551     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13552   } else {
13553   if(nrCastlingRights) {
13554      q = p;
13555      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13556        /* [HGM] write directly from rights */
13557            if(castlingRights[move][2] >= 0 &&
13558               castlingRights[move][0] >= 0   )
13559                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13560            if(castlingRights[move][2] >= 0 &&
13561               castlingRights[move][1] >= 0   )
13562                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13563            if(castlingRights[move][5] >= 0 &&
13564               castlingRights[move][3] >= 0   )
13565                 *p++ = castlingRights[move][3] + AAA;
13566            if(castlingRights[move][5] >= 0 &&
13567               castlingRights[move][4] >= 0   )
13568                 *p++ = castlingRights[move][4] + AAA;
13569      } else {
13570
13571         /* [HGM] write true castling rights */
13572         if( nrCastlingRights == 6 ) {
13573             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13574                castlingRights[move][2] >= 0  ) *p++ = 'K';
13575             if(castlingRights[move][1] == BOARD_LEFT &&
13576                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13577             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13578                castlingRights[move][5] >= 0  ) *p++ = 'k';
13579             if(castlingRights[move][4] == BOARD_LEFT &&
13580                castlingRights[move][5] >= 0  ) *p++ = 'q';
13581         }
13582      }
13583      if (q == p) *p++ = '-'; /* No castling rights */
13584      *p++ = ' ';
13585   }
13586
13587   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13588      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13589     /* En passant target square */
13590     if (move > backwardMostMove) {
13591         fromX = moveList[move - 1][0] - AAA;
13592         fromY = moveList[move - 1][1] - ONE;
13593         toX = moveList[move - 1][2] - AAA;
13594         toY = moveList[move - 1][3] - ONE;
13595         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13596             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13597             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13598             fromX == toX) {
13599             /* 2-square pawn move just happened */
13600             *p++ = toX + AAA;
13601             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13602         } else {
13603             *p++ = '-';
13604         }
13605     } else {
13606         *p++ = '-';
13607     }
13608     *p++ = ' ';
13609   }
13610   }
13611
13612     /* [HGM] find reversible plies */
13613     {   int i = 0, j=move;
13614
13615         if (appData.debugMode) { int k;
13616             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13617             for(k=backwardMostMove; k<=forwardMostMove; k++)
13618                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13619
13620         }
13621
13622         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13623         if( j == backwardMostMove ) i += initialRulePlies;
13624         sprintf(p, "%d ", i);
13625         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13626     }
13627     /* Fullmove number */
13628     sprintf(p, "%d", (move / 2) + 1);
13629     
13630     return StrSave(buf);
13631 }
13632
13633 Boolean
13634 ParseFEN(board, blackPlaysFirst, fen)
13635     Board board;
13636      int *blackPlaysFirst;
13637      char *fen;
13638 {
13639     int i, j;
13640     char *p;
13641     int emptycount;
13642     ChessSquare piece;
13643
13644     p = fen;
13645
13646     /* [HGM] by default clear Crazyhouse holdings, if present */
13647     if(gameInfo.holdingsWidth) {
13648        for(i=0; i<BOARD_HEIGHT; i++) {
13649            board[i][0]             = EmptySquare; /* black holdings */
13650            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13651            board[i][1]             = (ChessSquare) 0; /* black counts */
13652            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13653        }
13654     }
13655
13656     /* Piece placement data */
13657     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13658         j = 0;
13659         for (;;) {
13660             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13661                 if (*p == '/') p++;
13662                 emptycount = gameInfo.boardWidth - j;
13663                 while (emptycount--)
13664                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13665                 break;
13666 #if(BOARD_SIZE >= 10)
13667             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13668                 p++; emptycount=10;
13669                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13670                 while (emptycount--)
13671                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13672 #endif
13673             } else if (isdigit(*p)) {
13674                 emptycount = *p++ - '0';
13675                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13676                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13677                 while (emptycount--)
13678                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13679             } else if (*p == '+' || isalpha(*p)) {
13680                 if (j >= gameInfo.boardWidth) return FALSE;
13681                 if(*p=='+') {
13682                     piece = CharToPiece(*++p);
13683                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13684                     piece = (ChessSquare) (PROMOTED piece ); p++;
13685                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13686                 } else piece = CharToPiece(*p++);
13687
13688                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13689                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13690                     piece = (ChessSquare) (PROMOTED piece);
13691                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13692                     p++;
13693                 }
13694                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13695             } else {
13696                 return FALSE;
13697             }
13698         }
13699     }
13700     while (*p == '/' || *p == ' ') p++;
13701
13702     /* [HGM] look for Crazyhouse holdings here */
13703     while(*p==' ') p++;
13704     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13705         if(*p == '[') p++;
13706         if(*p == '-' ) *p++; /* empty holdings */ else {
13707             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13708             /* if we would allow FEN reading to set board size, we would   */
13709             /* have to add holdings and shift the board read so far here   */
13710             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13711                 *p++;
13712                 if((int) piece >= (int) BlackPawn ) {
13713                     i = (int)piece - (int)BlackPawn;
13714                     i = PieceToNumber((ChessSquare)i);
13715                     if( i >= gameInfo.holdingsSize ) return FALSE;
13716                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13717                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13718                 } else {
13719                     i = (int)piece - (int)WhitePawn;
13720                     i = PieceToNumber((ChessSquare)i);
13721                     if( i >= gameInfo.holdingsSize ) return FALSE;
13722                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13723                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13724                 }
13725             }
13726         }
13727         if(*p == ']') *p++;
13728     }
13729
13730     while(*p == ' ') p++;
13731
13732     /* Active color */
13733     switch (*p++) {
13734       case 'w':
13735         *blackPlaysFirst = FALSE;
13736         break;
13737       case 'b': 
13738         *blackPlaysFirst = TRUE;
13739         break;
13740       default:
13741         return FALSE;
13742     }
13743
13744     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13745     /* return the extra info in global variiables             */
13746
13747     /* set defaults in case FEN is incomplete */
13748     FENepStatus = EP_UNKNOWN;
13749     for(i=0; i<nrCastlingRights; i++ ) {
13750         FENcastlingRights[i] =
13751             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13752     }   /* assume possible unless obviously impossible */
13753     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13754     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13755     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13756     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13757     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13758     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13759     FENrulePlies = 0;
13760
13761     while(*p==' ') p++;
13762     if(nrCastlingRights) {
13763       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13764           /* castling indicator present, so default becomes no castlings */
13765           for(i=0; i<nrCastlingRights; i++ ) {
13766                  FENcastlingRights[i] = -1;
13767           }
13768       }
13769       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13770              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13771              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13772              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13773         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13774
13775         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13776             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13777             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13778         }
13779         switch(c) {
13780           case'K':
13781               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13782               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13783               FENcastlingRights[2] = whiteKingFile;
13784               break;
13785           case'Q':
13786               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13787               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13788               FENcastlingRights[2] = whiteKingFile;
13789               break;
13790           case'k':
13791               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13792               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13793               FENcastlingRights[5] = blackKingFile;
13794               break;
13795           case'q':
13796               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13797               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13798               FENcastlingRights[5] = blackKingFile;
13799           case '-':
13800               break;
13801           default: /* FRC castlings */
13802               if(c >= 'a') { /* black rights */
13803                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13804                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13805                   if(i == BOARD_RGHT) break;
13806                   FENcastlingRights[5] = i;
13807                   c -= AAA;
13808                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13809                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13810                   if(c > i)
13811                       FENcastlingRights[3] = c;
13812                   else
13813                       FENcastlingRights[4] = c;
13814               } else { /* white rights */
13815                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13816                     if(board[0][i] == WhiteKing) break;
13817                   if(i == BOARD_RGHT) break;
13818                   FENcastlingRights[2] = i;
13819                   c -= AAA - 'a' + 'A';
13820                   if(board[0][c] >= WhiteKing) break;
13821                   if(c > i)
13822                       FENcastlingRights[0] = c;
13823                   else
13824                       FENcastlingRights[1] = c;
13825               }
13826         }
13827       }
13828     if (appData.debugMode) {
13829         fprintf(debugFP, "FEN castling rights:");
13830         for(i=0; i<nrCastlingRights; i++)
13831         fprintf(debugFP, " %d", FENcastlingRights[i]);
13832         fprintf(debugFP, "\n");
13833     }
13834
13835       while(*p==' ') p++;
13836     }
13837
13838     /* read e.p. field in games that know e.p. capture */
13839     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13840        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13841       if(*p=='-') {
13842         p++; FENepStatus = EP_NONE;
13843       } else {
13844          char c = *p++ - AAA;
13845
13846          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13847          if(*p >= '0' && *p <='9') *p++;
13848          FENepStatus = c;
13849       }
13850     }
13851
13852
13853     if(sscanf(p, "%d", &i) == 1) {
13854         FENrulePlies = i; /* 50-move ply counter */
13855         /* (The move number is still ignored)    */
13856     }
13857
13858     return TRUE;
13859 }
13860       
13861 void
13862 EditPositionPasteFEN(char *fen)
13863 {
13864   if (fen != NULL) {
13865     Board initial_position;
13866
13867     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13868       DisplayError(_("Bad FEN position in clipboard"), 0);
13869       return ;
13870     } else {
13871       int savedBlackPlaysFirst = blackPlaysFirst;
13872       EditPositionEvent();
13873       blackPlaysFirst = savedBlackPlaysFirst;
13874       CopyBoard(boards[0], initial_position);
13875           /* [HGM] copy FEN attributes as well */
13876           {   int i;
13877               initialRulePlies = FENrulePlies;
13878               epStatus[0] = FENepStatus;
13879               for( i=0; i<nrCastlingRights; i++ )
13880                   castlingRights[0][i] = FENcastlingRights[i];
13881           }
13882       EditPositionDone();
13883       DisplayBothClocks();
13884       DrawPosition(FALSE, boards[currentMove]);
13885     }
13886   }
13887 }